package com.atwisdom.star.core.bsl.customized.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.atwisdom.star.common.util.DateUtils;
import com.atwisdom.star.common.util.JsonUtil;
import com.atwisdom.star.common.util.LocalDateTimeUtil;
import com.atwisdom.star.common.vo.ModelType;
import com.atwisdom.star.common.vo.StatusCode;
import com.atwisdom.star.core.bsl.customized.StockBslService;
import com.atwisdom.star.core.bsl.modelClass.logic.LogicInstanceBslService;
import com.atwisdom.star.core.dao.pojo.modelClass.hier.HierInstance;
import com.atwisdom.star.core.dsl.functionClass.BatchNumberDslService;
import com.atwisdom.star.core.dsl.modelClass.consume.ConsumeInstanceDslService;
import com.atwisdom.star.core.dsl.modelClass.hier.HierInstanceDslService;
import com.atwisdom.star.core.dsl.modelClass.logic.LogicInstanceDslService;
import com.atwisdom.star.core.dsl.modelClass.twins.TwinsInstanceDslService;
import com.atwisdom.star.exception.pojo.BusinessException;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.SerializationUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.Serializable;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * 
 * @Date: 2023-08-28 10:54
 */
@Slf4j
@AllArgsConstructor
@Service
public class StockBslServiceImpl implements StockBslService {

    private LogicInstanceDslService logicInstanceDslService;
    private ConsumeInstanceDslService consumeInstanceDslService;
    private TwinsInstanceDslService twinsInstanceDslService;
    private HierInstanceDslService hierInstanceDslService;
    private BatchNumberDslService batchNumberDslService;
    private LogicInstanceBslService logicInstanceBslService;
    // private AutoGenUtil autoGenUtil;

    /**
     * 通过bcode与quantity，确定可以出库的记录,
     * 再通过kanbanCode确定出库的数据是否符合要求
     * @param dataJson  主要参数有两个， 分别是{[{ material_bcode， quantity}], kanbancode}
     */
    @Override
    public JSONObject predicateBoard(JSONObject dataJson) {
        List<JSONObject> availableStockDetailList = queryAvailableStockDetail(dataJson);
        String kanbanCode = dataJson.getString("kanbancode");
        List<Object> dataList = availableStockDetailList.stream()
                .flatMap(d -> d.getJSONArray("data").stream()).collect(Collectors.toList());
        Optional<Object> optional = dataList.stream()
                .filter(d -> kanbanCode.equals((JsonUtil.toJSONObject(d)).getString("kanbancode")))
                .findFirst();
        JSONObject resultJson = new JSONObject();
        resultJson.put("isPresent", optional.isPresent());
        resultJson.put("data", optional.orElse(null));
        return resultJson;
    }

    /**
     * 通过bcode与quantity，确定可以出库的记录,
     * 再通过bin_id确定出库的数据是否符合要求
     * @param dataJson  主要参数有两个， 分别是{[{ material_bcode， quantity}], bin_id}
     * @return
     */
    @Override
    public List<JSONObject> queryByBinId(JSONObject dataJson) {
        List<JSONObject> availableStockDetailList = queryAvailableStockDetail(dataJson);
        JSONArray paramJsonList = dataJson.getJSONArray("data");
        List<JSONObject> resultList = new ArrayList<>();
        for (int i = 0; i < paramJsonList.size(); i++) {
            JSONObject resultJson = filterByBinId(availableStockDetailList, paramJsonList, i);
            resultList.add(resultJson);
        }
        return resultList;
    }



    /**
     * 查询某物在指定库位上的数量
     * @param binId
     * @param materialBcode
     * @return
     */
    // public BigDecimal queryCount(Long binId, String materialBcode) {
    //     String condition = "material_bcode = '" + materialBcode + "'" + " and bin_id = " + binId;
    //     List<JSONObject> stockList = logicInstanceDslService.query(condition, "stock");
    //     BigDecimal actualQuantity = stockList.stream().map(d -> d.getJSONObject("values").getBigDecimal("quantity"))
    //             .reduce(BigDecimal.ZERO, BigDecimal::add);
    //     return actualQuantity;
    // }

    /**
     * 根据库位，过滤出合法的数据
     * @param availableStockDetailList
     * @param paramJsonList
     * @param i
     * @return
     */
    private JSONObject filterByBinId(List<JSONObject> availableStockDetailList, JSONArray paramJsonList, int i) {
        JSONObject paramJson = paramJsonList.getJSONObject(i);
        String materialBcode = paramJson.getString("material_bcode");
        BigDecimal quantity = paramJson.getBigDecimal("quantity");
        BigDecimal subQuantity = paramJson.getBigDecimal("subQuantity");
        Long binId= paramJson.getLong("bin_id");
        List<JSONObject> dataList = availableStockDetailList.stream()
                .flatMap(d -> d.getJSONArray("data").stream().map(e->JsonUtil.toJSONObject(e))).collect(Collectors.toList());
        BigDecimal quantitySum = dataList.stream()
                .filter(d -> binId.equals(d.getLong("bin_id")) && materialBcode.equals(d.getString("material_bcode")))
                .map(d -> d.getBigDecimal("available_quantity"))
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal subQuantitySum = dataList.stream()
                .filter(d -> binId.equals(d.getLong("bin_id")) && materialBcode.equals(d.getString("material_bcode")))
                .map(d -> d.getBigDecimal("available_subQuantity"))
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        List<JSONObject> filterDataList = dataList.stream()
                .filter(d -> binId.equals(d.getLong("bin_id")) && materialBcode.equals(d.getString("material_bcode")))
                .collect(Collectors.toList());
        JSONObject resultJson = new JSONObject();
        resultJson.put("material_bcode", materialBcode);
        if (quantity != null) {
            resultJson.put("isEnough", quantitySum.compareTo(quantity) >= 0);
        } else {
            resultJson.put("isEnough", subQuantitySum.compareTo(subQuantity) >= 0);
        }
        resultJson.put("actualQuantity", quantitySum);
        resultJson.put("expectQuantity", quantity);
        resultJson.put("actualSubQuantity", subQuantitySum);
        resultJson.put("expectSubQuantity", subQuantity);
        resultJson.put("data", filterDataList);
        return resultJson;
    }


    /**
     * 通过bcode与quantity，确定可以出库的记录
     * @param dataJson
     *     [
     *         {
     *             "material_bcode":"A31",
     *             "quantity":100
     *         }
     *     ]
     *
     */
    @Override
    public List<JSONObject> queryAvailableStockDetail(JSONObject dataJson) {
        List<JSONObject> resultList = new ArrayList<>();
        List<JSONObject> paramJsonList =  handleDuplicate(dataJson.getJSONArray("data"));
        for (int i = 0; i < paramJsonList.size(); i++) {
            JSONObject paramJson = paramJsonList.get(i);
            paramJson.put("needKanbancode", dataJson.getBoolean("needKanbancode"));
            paramJson.put("condition", dataJson.getString("condition"));
            JSONObject availableMap = filterAvailable(paramJson);
            resultList.add(availableMap);
        }
        return resultList;
    }


    /**
     * 创建分拣单
     *  @param dataJson
     *      [
     *          {
     *              "material_bcode":"A31",
     *              "quantity":100
     *          }
     *      ]
     * @return
     *
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public List<JSONObject> createSortingOrder(JSONObject dataJson) {
        List<JSONObject> resultList = new ArrayList<>();
        List<JSONObject> paramJsonList =  handleDuplicate(dataJson.getJSONArray("data"));
        for (int i = 0; i < paramJsonList.size(); i++) {
            JSONObject paramJson = paramJsonList.get(i);
            JSONObject availableJson = filterAvailable(paramJson);
            resultList.add(availableJson);
        }
        long notEnoughCount = resultList.stream().filter(d -> d.getBoolean("isEnough") == false).count();
        // 如果需要数量全都是足够的，则生成分拣单
        if (notEnoughCount == 0L) {
            String formType = dataJson.getString("formType");
            Long formId = dataJson.getLong("formId");
            addSortingOrder(formType, formId, resultList, paramJsonList);
        }
        return resultList;
    }

    /**
     * 添加分拣单数据
     * @param resultList 可以出库的明细数据
     * @param paramJsonList 需求明细，如：a 10, b 20
     */
    private void addSortingOrder(String formType, Long formId, List<JSONObject> resultList, List<JSONObject> paramJsonList) {
        // 添加主表数据
        JSONObject containerValues = new JSONObject();
        containerValues.put("code", "autogen.sortingOrder-YYYY");
        containerValues.put("form_type", formType);
        containerValues.put("form_id", formId);
        String className = "sortingOrder";
        JSONObject containerInstanceJson = logicInstanceDslService.add(ModelType.Logic.getCode(), className, null, containerValues);
        Long containerId = containerInstanceJson.getLong("id");

        // 添加子表
        String childParentId = "2p7zx84m0c0000";
        String detailParentId = "5c7yo24eqas000";
        for (int i = 0; i < paramJsonList.size(); i++) {
            JSONObject childValues = paramJsonList.get(i);
            JSONObject childJson = new JSONObject();
            childJson.put("values", childValues);
            childJson.put("containerId", containerId);
            childJson.put("containerModel", ModelType.Logic.getCode());
            childJson.put("parentId", childParentId);
            hierInstanceDslService.addChild(childJson);
        }
        // 添加子子表数据，即可以出库的明细数据
        for (JSONObject availableJson : resultList) {
            JSONArray detailArray = availableJson.getJSONArray("data");
            for (int i = 0; i < detailArray.size(); i++) {
                JSONObject detailVales = detailArray.getJSONObject(i);
                // 表示未扫码
                detailVales.put("status", 0);
                detailVales.put("quantity", detailVales.get("available_quantity"));
                detailVales.put("subQuantity", detailVales.get("available_subQuantity"));
                detailVales.fluentRemove("available_quantity")
                        .fluentRemove("form_id")
                        .fluentRemove("form_type")
                        .fluentRemove("id")
                        .fluentRemove("available_subQuantity");
                JSONObject childJson = new JSONObject();
                childJson.put("values", detailVales);
                childJson.put("containerId", containerId);
                childJson.put("containerModel", ModelType.Logic.getCode());
                childJson.put("parentId", detailParentId);
                hierInstanceDslService.addChild(childJson);
            }
        }
    }


    /**
     * 查询强制出库的明细
     * @param dataJson
     * @return
     */
    @Override
    public List<JSONObject> queryForceStockDetail(JSONObject dataJson) {
        List<JSONObject> resultList = new ArrayList<>();
        JSONArray paramJsonArray = dataJson.getJSONArray("data");
        for (int i = 0; i < paramJsonArray.size(); i++) {
            JSONObject paramJson = paramJsonArray.getJSONObject(i);
            JSONObject availableMap = filterForceAvailable(paramJson);
            resultList.add(availableMap);
        }
        return resultList;
    }

    /**
     * 入库
     * 入库逻辑：
     *  1. 根据背番，查询物料明细， className = 'material"
     *  2. 根据binId，查询仓库信息
     *  3. 增加库存明细
     *  4. 增加库存数据
     *
     * @param dataJson
     *     [
     *         {
     *             "material_bcode":"A31",
     *             "quantity":100,
     *             "bin_id": 1
     *         }
     *     ]
     * 年月日+5位流水号-4位流水号, 例: 2023092700001-0001, 注:先进先出校验，只考“-”前边的一串数字
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void inBound(JSONObject dataJson) {
        Integer formType = dataJson.getInteger("formType");
        Long formId = dataJson.getLong("formId");
        String batchNumber = dataJson.getString("batch_number");
        if (StrUtil.isBlank(batchNumber)) {
            batchNumber = fetchStockBatchNumber();
        }
        JSONArray dataArray = dataJson.getJSONArray("data");
        LocalDateTime firstTime = LocalDateTime.now();
        List<JSONObject> inBoundDetailList = buildInBoundDetailList(formType, formId, batchNumber, dataArray);
        LocalDateTime secondTime = LocalDateTime.now();
        long between = ChronoUnit.SECONDS.between(firstTime, secondTime);
        System.out.println(between);
        List<JSONObject>  tempInBoundDetailList = (List<JSONObject>) SerializationUtils.clone((Serializable) inBoundDetailList);
        // 因为是新批次，所以直接增加入库记录
        logicInstanceDslService.batchAddOrUpdate(tempInBoundDetailList);
         // 更新看板状态
        updateKanbanToAvailable(inBoundDetailList);
    }

    /**
     * 更新看板为占用
     * @param inBoundDetailList
     */
    private void updateKanbanToAvailable(List<JSONObject> inBoundDetailList) {
        if (CollectionUtil.isEmpty(inBoundDetailList)) {
            return;
        }

        List<String> kanbancodeList = new ArrayList<>();
        List<JSONObject> jsonDataList = new ArrayList<>();
        inBoundDetailList.forEach(inBoundDetail -> {
            JSONObject inBoundDetailValues = inBoundDetail.getJSONObject("values");
            if (inBoundDetailValues == null) {
                inBoundDetailValues = inBoundDetail;
            }
            String kanbancode = inBoundDetailValues.getString("kanbancode");
            if (StrUtil.isNotBlank(kanbancode)) {
                kanbancodeList.add(kanbancode);
                String date = DateUtils.getDateFormat(new Date(), "yyyy-MM-dd 00:00:00");
                JSONObject kanbanDataJson = new JSONObject();
                kanbanDataJson.put("status", 1);
                kanbanDataJson.put("citeStatus", true);
                kanbanDataJson.put("source_id", inBoundDetail.get("id"));
                kanbanDataJson.put("source_code", inBoundDetailValues.get("code"));
                kanbanDataJson.put("warehouse_id", inBoundDetailValues.get("warehouse_id"));
                kanbanDataJson.put("warehouse_code", inBoundDetailValues.get("warehouse_code"));
                kanbanDataJson.put("warehouse_name", inBoundDetailValues.get("warehouse_name"));
                kanbanDataJson.put("bin_id", inBoundDetailValues.get("bin_id"));
                kanbanDataJson.put("bin_code", inBoundDetailValues.get("bin_code"));
                kanbanDataJson.put("bin_name", inBoundDetailValues.get("bin_name"));
                kanbanDataJson.put("date", date);
                kanbanDataJson.put("kanbanCode", kanbancode);
                jsonDataList.add(kanbanDataJson);
            }
        });

        if (CollectionUtil.isEmpty(jsonDataList)) {
            return;
        }

        this.logicInstanceDslService.updateBatchBy("bulletinBoard", jsonDataList, "kanbanCode", kanbancodeList);
    }


    /**
     * 更新看板为可用
     * @param outBoundDetailList
     */
    private void updateOutBoundUnAvailable(List<JSONObject> outBoundDetailList, Set<String> kanbanCodeSet ) {
        Set<String> kanbanSet = new HashSet<>();
        for (JSONObject outBoundDetail : outBoundDetailList) {
            String kanbancode = outBoundDetail.getString("kanbancode");
            if (StrUtil.isNotBlank(kanbancode) && kanbanCodeSet.contains(kanbancode)) {
                updateBulletinBoard(kanbancode, kanbanSet);
            }
        }
    }

    /**
     * 更新看板为可用
     * @param outBoundDetailList
     */
    private void updateKanbanToUnAvailable(List<JSONObject> outBoundDetailList) {
        Set<String> kanbanSet = new HashSet<>();
        for (JSONObject outBoundDetail : outBoundDetailList) {
            String kanbancode = outBoundDetail.getString("kanbancode");
            updateBulletinBoard(kanbancode, kanbanSet);
        }
    }

    private void updateBulletinBoard(String kanbancode, Set<String> kanbanSet) {
        if (StrUtil.isNotBlank(kanbancode) && !kanbanSet.contains(kanbancode)) {
            kanbanSet.add(kanbancode);
            String condition = " kanbanCode = '" + kanbancode + "'";
            JSONObject kanbanDataJson = new JSONObject();
            kanbanDataJson.put("status",0);
            kanbanDataJson.put("citeStatus",false);
            kanbanDataJson.put("source_id",null);
            kanbanDataJson.put("source_code",null);
            kanbanDataJson.put("warehouse_id", null);
            kanbanDataJson.put("warehouse_code", null);
            kanbanDataJson.put("warehouse_name", null);
            kanbanDataJson.put("bin_id", null);
            kanbanDataJson.put("bin_code", null);
            kanbanDataJson.put("bin_name", null);
            kanbanDataJson.put("date", null);

            JSONObject bulletinBoardJson = new JSONObject();
            bulletinBoardJson.put("className","bulletinBoard");
            bulletinBoardJson.put("type","logic");
            bulletinBoardJson.put("values",kanbanDataJson);
            logicInstanceDslService.updateByCondition("bulletinBoard", condition, bulletinBoardJson);
        }
    }

    /**
     * 生成入库批次号
     // * @param format  默认"autogen.yyyyMMdd"
     * @return
     */
    @Override
    public String fetchStockBatchNumber() {
        // if (StrUtil.isBlank(format)) {
        //     format = "autogen.yyyyMMdd";
        // }
        String batchNumber = batchNumberDslService.fetchAutoGen("autogen.yyyyMMdd");
        String[] batchNumberArray = batchNumber.split("-");
        batchNumber = batchNumberArray[0] + "-" + StrUtil.padPre(batchNumberArray[1], 5, "000000");
        return batchNumber;
    }

    /**
     * 生成入库小批次号
     * @param batchNumber 大批次号
     * @return
     */
    @Override
    public String fetchStockSubBatchNumber(String batchNumber) {
        String subBatchNumber =  batchNumberDslService.getNextVal(batchNumber);
        String[] subBatchNumberArray = subBatchNumber.split("-");
        subBatchNumber = batchNumber + "-" + StrUtil.padPre(subBatchNumberArray[2], 5, "000000");
        return subBatchNumber;
    }

    /**
     * 
     * 废品入库
     *
     * @param dataJson
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void inBoundToWaster(JSONObject dataJson) {
        Long binId = fetchWasterBinId();
        inBoundWithBinId(dataJson, binId);
    }

    /**
     *
     * 不良品入库
     * 
     * @param dataJson
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void inBoundToDefective(JSONObject dataJson) {
        Long binId = fetchDefectiveBinId();
        inBoundWithBinId(dataJson, binId);
    }

    /**
     *
     * 待检区入库
     * 
     * @param dataJson
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void inBoundToWaitCheck(JSONObject dataJson) {
        Long binId = fetchWaitCheckBinId();
        inBoundWithBinId(dataJson, binId);
    }

    /**
     *
     * 不良品入库
     * 
     * @param dataJson
     */
    @Transactional(rollbackFor = Exception.class)
    public void inBoundWithBinId(JSONObject dataJson,  Long binId ) {
        JSONArray dataArray = dataJson.getJSONArray("data");
        if (binId == null) {
            throwException("废品库位不存在");
        }
        for (int i = 0; i < dataArray.size(); i++) {
            JSONObject json = dataArray.getJSONObject(i);
            Long paramBinId = json.getLong("bin_id");
            if(paramBinId == null){
                json.put("bin_id", binId);
            }
        }
        inBound(dataJson);
    }

    /**
     * 查询废品库
     * @return
     */
    private Long fetchWasterBinId() {
        String condition = "wasterFlag = 1";
        return fetchBinId(condition);
    }

    /**
     * 查询待检区库
     * @return
     */
    private Long fetchWaitCheckBinId() {
        String condition = "waitCheckFlag = 1";
        return fetchBinId(condition);
    }

    /**
     * 查询不良品库
     * @return
     */
    private Long fetchDefectiveBinId() {
        String condition = "defectiveFlag = 1";
        return fetchBinId(condition);
    }

    /**
     * 查询不良品库
     * @return
     */
    private List<Long> fetchUnavailableWarehouseId() {
        String condition = "wasterFlag = 1 and defectiveFlag = 1 and lineFlag = 1";
        List<HierInstance> warehouseList = hierInstanceDslService.query("twins", "warehouse", condition);
        return warehouseList.stream().map(d->d.getId()).collect(Collectors.toList());
    }

    /**
     * 查询库位
     * @return
     */
    private Long fetchBinId(String condition) {
        List<HierInstance> binList = fetchBinList(condition);
        Long binId = binList.get(0).getId();
        return binId;
    }

    /**
     * 查询库位
     * @return
     */
    public List<HierInstance> fetchBinList(String warehouseCondition) {
        JSONObject paramJson = new JSONObject();
        paramJson.put("condition", warehouseCondition);
        return fetchBinListByWarehouseCondition(paramJson);
    }


    /**
     * 查询库位
     * @return
     */
    @Override
    public List<HierInstance> fetchBinListByWarehouseCondition(JSONObject paramJson) {
        String condition = paramJson.getString("condition");
        List<HierInstance> warehouseList = hierInstanceDslService.query("twins", "warehouse", condition);
        if (CollectionUtil.isEmpty(warehouseList)) {
            return null;
        }
        HierInstance warehouse = warehouseList.get(0);
        String binCondition = "warehouse_id = " + warehouse.getId();
        List<HierInstance> binList = hierInstanceDslService.query("twins", "bin", binCondition);
        if (CollectionUtil.isEmpty(binList)) {
            return null;
        }
        return binList;
    }



    /**
     * 查询禁止出库的库位
     * @return
     */
    private List<Long> fetchForbiddenOutBinId() {
        List<Long> binIdList = new ArrayList();
        String condition = "outFlag = 0";
        List<HierInstance> warehouseList = hierInstanceDslService.query("twins", "warehouse", condition);
        if (CollectionUtil.isNotEmpty(warehouseList)) {
            String warehouseIdListStr = warehouseList.stream().map(d -> String.valueOf(d.getId())).collect(Collectors.joining(","));
            String binCondition = "warehouse_id in ("+warehouseIdListStr+") ";
            List<HierInstance> binList = hierInstanceDslService.query("twins", "bin", binCondition);
            binIdList = binList.stream().map(d->d.getId()).collect(Collectors.toList());
        }
        return binIdList;
    }

    /**
     * 由调用方指定批次号的入库业务
     * 1. 生产出的产品入库操作
     * @param dataJson
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void inBoundWithBatchNumber(JSONObject dataJson) {
        int formType = dataJson.getIntValue("formType");
        long formId = dataJson.getLongValue("formId");
        JSONArray dataArray = dataJson.getJSONArray("data");
        List<JSONObject> inBoundDetailList = buildInBoundDetailListWithBatchNumber(formType, formId, dataArray);
        // List<JSONObject> inBoundStockList = buildInBoundStock(BeanUtil.copyToList(inBoundDetailList, JSONObject.class));
        // 因为是新批次，所以直接增加入库记录
        logicInstanceDslService.batchAddOrUpdate(inBoundDetailList);
        // logicInstanceDslService.batchAddOrUpdate(inBoundStockList);
    }


    /**
     * 带批次号的，强制入库，比如：退料逻辑
     * 入库逻辑：
     *  1. 根据背番，批次号，仓库，查询入库明细
     *  2. 按照明细中的单位转换率，将明细的数量更新成换算后的值
     *  3. 增加库存明细
     *  4. 更新库存数据
     * @param dataJson
     *     [
     *         {
     *             "material_bcode":"A31",
     *             "quantity":100
     *             "bin_id": 1,
     *             "batch_number":"11111"
     *         }
     *     ]
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void inBoundForce(JSONObject dataJson) {
        List<JSONObject> inBoundDetailList = new ArrayList<>();
        int formType = dataJson.getIntValue("formType");
        long formId = dataJson.getLongValue("formId");
        JSONArray dataArray = dataJson.getJSONArray("data");
        for (int i = 0; i < dataArray.size(); i++) {
            JSONObject data = dataArray.getJSONObject(i);
            BigDecimal quantity = data.getBigDecimal("quantity");
            BigDecimal subQuantity = data.getBigDecimal("subQuantity");
            String batchNumber = data.getString("batch_number");
            String kanbancode = data.getString("kanbancode");
            String materialBcode = data.getString("material_bcode");
            Long binId = data.getLong("bin_id");
            // 查询库位数据
            JSONObject binJson = hierInstanceDslService.queryById(binId);
            JSONObject stockDetailJson = fetchOneStockDetail(materialBcode, batchNumber, binId);
            removeOldData(stockDetailJson);
            JSONObject stockDetailValue = stockDetailJson.getJSONObject("values");
            BigDecimal conversionRate = stockDetailValue.getBigDecimal("conversionRate");
            // 如果有主单位，直接刷新辅单位
            if (quantity != null) {
                subQuantity = quantity.divide(conversionRate, 2, RoundingMode.HALF_UP);
            } else if (subQuantity != null) {
                // 如果只有辅单位,主单位为空，刷新主单位
                quantity = subQuantity.multiply(conversionRate).setScale(2, RoundingMode.HALF_UP);
            } else {
                // 如果两个数量都没有，报错
                throwException("背番号["+materialBcode+"]入库数量不能为空");
            }
            JsonUtil.merge(stockDetailValue, binJson);
            JSONObject binValueJson = binJson.getJSONObject("values");
            stockDetailValue.put("warehouse_id", binValueJson.getLong("warehouse_id"));
            stockDetailValue.put("warehouse_code", binValueJson.getString("warehouse_code"));
            stockDetailValue.put("warehouse_name",  binValueJson.getString("warehouse_name"));
            stockDetailValue.put("bin_id", binId);
            stockDetailValue.put("bin_code",  binValueJson.getString("code"));
            stockDetailValue.put("bin_name",  binValueJson.getString("name"));

            stockDetailValue.put("form_type", formType);
            stockDetailValue.put("form_id", formId);
            stockDetailValue.put("kanbancode", kanbancode);
            stockDetailValue.put("quantity", quantity);
            stockDetailValue.put("subQuantity", subQuantity);
            stockDetailValue.put("available_quantity", quantity);
            stockDetailValue.put("available_subQuantity", subQuantity);
            inBoundDetailList.add(stockDetailJson);
        }
        List<JSONObject> inBoundStockList = buildUpdateInBoundStock(inBoundDetailList);
        // 增加入库记录
        logicInstanceDslService.batchAddOrUpdate(inBoundDetailList);
        logicInstanceDslService.batchAddOrUpdate(inBoundStockList);
    }

    /**
     * 删除掉不需要的数据
     * @param stockDetailJson
     */
    private void removeOldData(JSONObject stockDetailJson) {
        stockDetailJson.remove("id");
        stockDetailJson.remove("createdBy");
        stockDetailJson.remove("createTime");
        stockDetailJson.remove("updatedBy");
        stockDetailJson.remove("updateTime");
    }


    /**
     * 构建入库明细，用于构建调用方指定批次号的数据，
     * 如：生产产品入库
     * @param formType
     * @param formId
     * @param dataArray 明细中需要带批次号
     * @return
     */
    private List<JSONObject> buildInBoundDetailListWithBatchNumber(int formType, long formId, JSONArray dataArray) {
        List<JSONObject> inBoundDetailList = new ArrayList<>();
        for (int i = 0; i < dataArray.size(); i++) {
            JSONObject paramJson = dataArray.getJSONObject(i);
            String batchNumber  = paramJson.getString("batchNumber");
            Long binId = paramJson.getLong("bin_id");
            JSONObject binJson = fetchOneBin(binId);
            JSONObject binJsonValues = binJson.getJSONObject("values");
            String materialBcode = paramJson.getString("material_bcode");
            BigDecimal quantity = paramJson.getBigDecimal("quantity");
            BigDecimal subQuantity = paramJson.getBigDecimal("subQuantity");
            Long formSubId = paramJson.getLong("form_sub_id");
            JSONObject materialJson = fetchOneMaterial(materialBcode);
            BigDecimal conversionRate = null;
            // 要先查询批次号是否存在，即之前同批次是否入过库
            JSONObject originStockDetail = fetchOneStockDetailOrNull(materialBcode, batchNumber, binId);
            // 如果入过，那么转换率与之前一样
            if (originStockDetail != null) {
                JSONObject originDetailValue = originStockDetail.getJSONObject("values");
                conversionRate = originDetailValue.getBigDecimal("conversionRate");
            } else {
                conversionRate = materialJson.getJSONObject("values").getBigDecimal("conversionRate");
            }
            if (conversionRate == null && (quantity == null || subQuantity == null)) {
                throwException("背番号[" + materialBcode + "] 主辅单位转换率为空时，入库主单位与辅单位数量不能为空");
            }
            if (quantity == null && subQuantity != null && conversionRate != null) {
                quantity = subQuantity.multiply(conversionRate).setScale(2, RoundingMode.HALF_UP);
                paramJson.put("quantity", quantity);
            }
            if (subQuantity == null && quantity != null && conversionRate != null) {
                subQuantity = quantity.divide(conversionRate, 2, RoundingMode.HALF_UP);
                paramJson.put("subQuantity", subQuantity);
            }
            JSONObject inBoundDetail = buildInBoundStockDetail(formType, formId, formSubId, batchNumber, paramJson, binId, binJsonValues, materialJson);
            inBoundDetailList.add(inBoundDetail);
        }
        return inBoundDetailList;
    }

    /**
     * 构建入库明细
     * @param formType
     * @param formId
     * @param batchNumber
     * @param dataArray
     * @return
     */
    private List<JSONObject> buildInBoundDetailList(int formType, Long formId, String batchNumber, JSONArray dataArray) {
        List<JSONObject> inBoundDetailList = new ArrayList<>();
        Map<String, JSONObject> duplicateMap = new HashMap<>();
        for (int i = 0; i < dataArray.size(); i++) {
            JSONObject paramJson = dataArray.getJSONObject(i);
            // 查询老批次号
            String uniqueCode = paramJson.getString("uniqueCode");

            if (StrUtil.isNotBlank(uniqueCode)) {
                JSONObject stockDetail = fetchOneStockDetailByUniqueCode(uniqueCode);
                // paramJson.getString("batch_number")
                if (stockDetail != null && StrUtil.isBlank(paramJson.getString("batch_number"))) {
                    paramJson.put("batch_number", stockDetail.getJSONObject("values").getString("batch_number"));
                }
            }
            String materialBcode = paramJson.getString("material_bcode");
            BigDecimal quantity = paramJson.getBigDecimal("quantity");
             BigDecimal subQuantity = paramJson.getBigDecimal("subQuantity");
             if(subQuantity == null){
                 subQuantity = BigDecimal.ONE;
             }
            paramJson.put("subQuantity", subQuantity);
            Long binId = paramJson.getLong("bin_id");
            Long formSubId = paramJson.getLong("form_sub_id");
            JSONObject binJson = fetchOneBin(binId);
            if (binId == null || binJson == null) {
                throwException("背番号[" + materialBcode + "] 库位不能为空");
            }
            JSONObject binJsonValues = binJson.getJSONObject("values");
            BigDecimal conversionRate = paramJson.getBigDecimal("conversionRate");
            JSONObject materialJson = fetchOneMaterial(materialBcode);
            if(conversionRate == null){
                conversionRate = materialJson.getJSONObject("values").getBigDecimal("conversionRate");
            }

            if (conversionRate == null && (quantity != null || subQuantity != null)) {
                if(quantity == null){
                    String msg = "背番号[" + materialBcode + "] 主辅单位转换率为空时，入库主单位数量不能为空";
                    System.out.println(msg);
                    throwException(msg);
                }
                conversionRate = quantity.divide(subQuantity, 2, RoundingMode.HALF_UP);
            }

            if (quantity == null && subQuantity != null && conversionRate != null) {
                quantity = subQuantity.multiply(conversionRate).setScale(2, RoundingMode.HALF_UP);
                paramJson.put("quantity", quantity);
            }

            String subBatchNumber = "";
            if ( StrUtil.isNotBlank(paramJson.getString("batch_number"))) {
                subBatchNumber = paramJson.getString("batch_number");
            } else {
                subBatchNumber = fetchStockSubBatchNumber(batchNumber);
            }
            paramJson.put("conversionRate",conversionRate);
            JSONObject inBoundDetail = buildInBoundStockDetail(formType, formId, formSubId, subBatchNumber, paramJson, binId, binJsonValues, materialJson);
            inBoundDetailList.add(inBoundDetail);

            // 以上代码原来用于合并请求入库的数据中有相同的物料，后又改为相同物料单位转换率可能不同，所以不能合并
            // String kanbancode = paramJson.getString("kanbancode");
            // String key = materialBcode + "#" + binId + "#" + kanbancode;
            // if (duplicateMap.containsKey(key)) {
            //     JSONObject existData = duplicateMap.get(key).getJSONObject("values");
            //     BigDecimal originQuantity = existData.getBigDecimal("quantity");
            //     BigDecimal originSubQuantity = existData.getBigDecimal("subQuantity");
            //     BigDecimal currentQuantity = paramJson.getBigDecimal("quantity");
            //     BigDecimal currentSubQuantity = paramJson.getBigDecimal("subQuantity");
            //     existData.put("quantity", originQuantity.add(currentQuantity));
            //     existData.put("available_quantity", originQuantity.add(currentQuantity));
            //     existData.put("subQuantity", originSubQuantity.add(currentSubQuantity));
            //     existData.put("available_subQuantity", originSubQuantity.add(currentSubQuantity));
            //     existData.put("conversionRate", (originSubQuantity.add(currentSubQuantity)).divide(originQuantity.add(currentQuantity), 2, RoundingMode.HALF_UP));
            // } else {
            //     JSONObject inBoundDetail = buildInBoundStockDetail(formType, formId, batchNumber, paramJson, binId, binJsonValues, materialJson);
            //     inBoundDetailList.add(inBoundDetail);
            //     duplicateMap.put(key, inBoundDetail);
            // }
        }
        //
        return inBoundDetailList;
    }

    /**
     * 构建入库记录
     * @param stockDetailList
     * @return
     */
    private List<JSONObject> buildInBoundStock(List<JSONObject> stockDetailList) {
        List<JSONObject> resultList = new ArrayList<>();
        Map<String, JSONObject> duplicateMap = new HashMap<>();
        stockDetailList.forEach(d->{
            JSONObject newDetail =  ObjectUtil.cloneByStream(d);
            newDetail.remove("id");
            newDetail.put("className", "stock");
            JSONObject values = newDetail.getJSONObject("values");
            values.remove("available_quantity");
            values.remove("kanbancode");
            Long binId = values.getLong("bin_id");
            String materialBcode = values.getString("material_bcode");
            String batchNumber = values.getString("batch_number");
            int count = StrUtil.count(batchNumber, "-");
            // 截取大批次号
            if (count == 2) {
                batchNumber = batchNumber.substring(0, batchNumber.lastIndexOf("-"));
                newDetail.getJSONObject("values").put("batch_number", batchNumber);
            }
            String key = materialBcode + "#" + binId + "#" + batchNumber;
            if (duplicateMap.containsKey(key)) {
                JSONObject existData = duplicateMap.get(key).getJSONObject("values");
                BigDecimal originQuantity = existData.getBigDecimal("quantity");
                BigDecimal currentQuantity = values.getBigDecimal("quantity");
                BigDecimal originSubQuantity = existData.getBigDecimal("subQuantity");
                BigDecimal currentSubQuantity = values.getBigDecimal("subQuantity");
                existData.put("quantity", originQuantity.add(currentQuantity));
                existData.put("subQuantity", originSubQuantity.add(currentSubQuantity));
            } else {
                duplicateMap.put(key, newDetail);
                resultList.add(BeanUtil.copyProperties(newDetail, JSONObject.class));
            }
        });
        return resultList;
    }
    /**
     * 构建用于更新的入库记录，只能在更新时使用
     * @param stockDetailList
     * @return
     */
    private List<JSONObject> buildUpdateInBoundStock(List<JSONObject> stockDetailList) {
        List<JSONObject> resultList = stockDetailList.stream().map(d->{
            JSONObject newDetail =  ObjectUtil.cloneByStream(d);
            JSONObject detailValues = newDetail.getJSONObject("values");
            String materialBcode = detailValues.getString("material_bcode");
            String batchNumber = detailValues.getString("batch_number");
            batchNumber = handleSubBatchNumber(batchNumber);
            Long binId = detailValues.getLong("bin_id");
            JSONObject stock = fetchInBoundOneStock(binId, batchNumber, materialBcode);
            if (stock == null) {
                // 说明是第一次入库
                stock = buildInBoundStock(Arrays.asList(d)).get(0);
            }
            JSONObject stockValues = stock.getJSONObject("values");
            BigDecimal originQuantity = stockValues.getBigDecimal("quantity");
            BigDecimal originSubQuantity = stockValues.getBigDecimal("subQuantity");
            stockValues.put("quantity", originQuantity.add(detailValues.getBigDecimal("quantity")));
            stockValues.put("subQuantity", originSubQuantity.add(detailValues.getBigDecimal("subQuantity")));
            return stock;
        }).collect(Collectors.toList());
        return resultList;
    }





    /**
     * 构建入库明细
     * @param formType
     * @param formId
     * @param batchNumber
     * @param paramJson
     * @param binId
     * @param binJsonValues
     * @param materialJson
     * @return
     */
    private JSONObject buildInBoundStockDetail(int formType, Long formId, Long formSubId, String batchNumber, JSONObject paramJson, Long binId, JSONObject binJsonValues, JSONObject materialJson) {
        JSONObject materialValues = materialJson.getJSONObject("values");
        JSONObject stockDetailValues = new JSONObject();
        stockDetailValues.put("material_id", materialJson.getLongValue("id"));
        stockDetailValues.put("material_pcode", materialValues.getString("pcode"));
        stockDetailValues.put("material_bcode", materialValues.getString("bcode"));
        stockDetailValues.put("material_name", materialValues.getString("name"));
        stockDetailValues.put("material_model", materialValues.getString("model"));
        stockDetailValues.put("material_spec", materialValues.getString("spec"));
        stockDetailValues.put("material_unit", materialValues.getString("unit"));
        stockDetailValues.put("material_subUnit", materialValues.getString("subUnit"));
        stockDetailValues.put("quantity", paramJson.getBigDecimal("quantity"));
        if (paramJson.getBigDecimal("subQuantity") == null || paramJson.getBigDecimal("subQuantity").compareTo(BigDecimal.ZERO) == 0) {
            paramJson.put("subQuantity", 1);
        }
        stockDetailValues.put("subQuantity", paramJson.getBigDecimal("subQuantity"));
        if(paramJson.getBigDecimal("conversionRate") != null && paramJson.getBigDecimal("conversionRate").compareTo(BigDecimal.ZERO) != 0){
            stockDetailValues.put("conversionRate", paramJson.getBigDecimal("conversionRate"));
        }else{
            stockDetailValues.put("conversionRate", paramJson.getBigDecimal("quantity").divide(paramJson.getBigDecimal("subQuantity"), 2, RoundingMode.HALF_UP));
        }
        stockDetailValues.put("available_quantity", paramJson.getBigDecimal("quantity"));
        stockDetailValues.put("available_subQuantity", paramJson.getBigDecimal("subQuantity"));
        stockDetailValues.put("uniqueCode", paramJson.getString("uniqueCode"));
        stockDetailValues.put("status", paramJson.get("status") == null ? 0 : paramJson.getString("status"));
        stockDetailValues.put("warehouse_keeper", binJsonValues.getString("warehouse_keeper"));
        stockDetailValues.put("warehouse_code", binJsonValues.getString("warehouse_code"));
        stockDetailValues.put("warehouse_name", binJsonValues.getString("warehouse_name"));
        stockDetailValues.put("warehouse_id", binJsonValues.getLongValue("warehouse_id"));
        stockDetailValues.put("bin_code", binJsonValues.getString("code"));
        stockDetailValues.put("bin_name", binJsonValues.getString("name"));
        stockDetailValues.put("bin_id", binId);
        stockDetailValues.put("form_type", formType);
        stockDetailValues.put("form_id", formId);
        stockDetailValues.put("form_sub_id", formSubId);
        stockDetailValues.put("stock_type", 1);
        stockDetailValues.put("batch_number", batchNumber);
        stockDetailValues.put("kanbancode", paramJson.getString("kanbancode"));
        stockDetailValues.put("code", paramJson.getString("code"));
        // stockDetailValues.put("capacity", paramJson.getIntValue("capacity"));
        // stockDetailValues.put("capacity_unit", paramJson.getString("capacity_unit"));
        JSONObject stockDetail = new JSONObject();
        stockDetail.put("values", stockDetailValues);
        stockDetail.put("className", "stockDetail");
        stockDetail.put("createTime", LocalDateTimeUtil.now());
        stockDetail.put("updateTime", LocalDateTimeUtil.now());
        stockDetail.put("className", "stockDetail");
        stockDetail.put("type", "logic");
        return stockDetail;
    }


    /**
     * 出库
     * 按bcode、批次，数量出库，合并数量后，再出库，校验是否先进先出
     * 出库逻辑，
     *     数据中已经包含了明细数据与明细ID， 所以根据明细数据直接扣减库存即可，但并不知道需要扣多少库存
     *     1. 扣减stock
     *     2. 扣减stockDetail可用数量
     *     3. 添加stockDetail扣减记录
     * @param dataJson
     *   "formType": 1,
     *   "formId": 11111,
     *   "data":    [
     *         {
     *             "material_bcode":"A31",
     *             "quantity":100,
     *             "batch_number": "batch-31",
     *             "bin_id": 1,
     *             "form_sub_id: 2,
     *         }
     *     ]
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<JSONObject> outBound(JSONObject dataJson) {
        List<JSONObject> availableStockDetailList = queryAvailableStockDetail(dataJson);
        predicateStockQuantity(availableStockDetailList);
        updateAllStockData(dataJson, availableStockDetailList);
        // return outBoundDetailList;
        return null;
    }

    /**
     * 强制出库
     * @param dataJson
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<JSONObject> outBoundForce(JSONObject dataJson) {
        List<JSONObject> availableStockDetailList = queryForceStockDetail(dataJson);
        predicateStockQuantity(availableStockDetailList);
        updateAllStockData(dataJson, availableStockDetailList);
        // return outBoundDetailList;
        return null;
    }

    /**
     * 更新所有库存相关的数据，包括库存明细与库存数据
     * @param dataJson 出库请求数据
     * @param availableStockDetailList 可以出库的数据
     */
    private void updateAllStockData(JSONObject dataJson, List<JSONObject> availableStockDetailList) {
        // 获取库存明细数据
        List<JSONObject> dataList = availableStockDetailList.stream()
                .flatMap(d -> d.getJSONArray("data").stream().map(e -> JsonUtil.toJSONObject(e))).collect(Collectors.toList());
        // 检查出库请求数据是否合法
        checkOutBoundData(dataJson, dataList);
        // 如果到这里还没有没有抛出异常，说明数据全部合法，可以正常出库，因为上面返回的数据中已经包含了明细数据与明细ID， 所以根据明细数据直接扣减库存即可
        // 扣减库存明细数据
        JSONArray expectOutBoundArray = dataJson.getJSONArray("data");
        // 操作扣减的业务类型与ID
        Integer formType = dataJson.getInteger("formType");
        Long formId = dataJson.getLong("formId");

        Set<String> kanbanCodeSet = new HashSet<>();
        // 扣减库存数据
        deductStockDetail(dataList, expectOutBoundArray, formType, formId,kanbanCodeSet);
        // 至此，dataList中包含了所有需要修改或添加的库存明细数据，进行修改即可
        updateSockDetail(dataList);
        // 更新看板状态
        updateOutBoundUnAvailable(dataList,kanbanCodeSet);

        // 更新库存数据
        // List<JSONObject> outBoundDetailList = updateStock(dataList);
        // return outBoundDetailList;
    }

    /**
     * 更新库存明细数据
     * dataList中包含了所有需要修改或添加的库存明细数据，进行修改或新增即可
     * @param dataList
     */
    private void updateSockDetail(List<JSONObject> dataList) {
        List<JSONObject> addOrUpdateList = dataList.stream().map(d -> {
            JSONObject tempData = new JSONObject();
            if (d.get("id") != null) {
                tempData.put("id", d.get("id"));
            }
            tempData.put("className", "stockDetail");
            tempData.put("type", "logic");
            tempData.put("createTime", LocalDateTimeUtil.now());
            tempData.put("updateTime", LocalDateTimeUtil.now());
            tempData.put("values", BeanUtil.copyProperties(d, JSONObject.class));
            return tempData;
        }).collect(Collectors.toList());
        logicInstanceDslService.batchAddOrUpdate(addOrUpdateList);
    }

    /**
     * 更新库存数据
     * @param dataList 出库明细
     * @return 返回出库明细数据，注意：只包括增加的明细数据，不包括更新的数据
     */
    // private List<JSONObject> updateStock(List<JSONObject> dataList) {
    //     List<JSONObject> deductDetailList = dataList.stream().filter(d -> d.getIntValue("stock_type") == 2).collect(Collectors.toList());
    //     deductDetailList.forEach(d->{
    //         Long binId = d.getLong("bin_id");
    //         String batchNumber = d.getString("batch_number");
    //         String materialBcode = d.getString("material_bcode");
    //         // 扣减stock数量，根据库位，批次， 背番，可以确定一条记录
    //         batchNumber = handleSubBatchNumber(batchNumber);
    //         JSONObject stockJson = fetchOneStock(binId, batchNumber, materialBcode);
    //         BigDecimal originQuantity = stockJson.getJSONObject("values").getBigDecimal("quantity");
    //         BigDecimal originSubQuantity = stockJson.getJSONObject("values").getBigDecimal("subQuantity");
    //         stockJson.getJSONObject("values").put("quantity", originQuantity.subtract(d.getBigDecimal("quantity").abs()));
    //         stockJson.getJSONObject("values").put("subQuantity", originSubQuantity.subtract(d.getBigDecimal("subQuantity").abs()));
    //         logicInstanceDslService.updateById(stockJson);
    //     });
    //     return deductDetailList;
    // }

    /**
     * 将小批次号处理成大批次号
     * @param subBatchNumber
     * @return
     */
    private String handleSubBatchNumber(String subBatchNumber) {
        int count = StrUtil.count(subBatchNumber, "-");
        // 截取大批次号
        if (count == 2) {
            subBatchNumber = subBatchNumber.substring(0, subBatchNumber.lastIndexOf("-"));
        }
        return subBatchNumber;
    }

    /**
     * 扣减库存明细
     * @param dataList 可以出库的数据
     * @param expectOutBoundArray 请求出库的数据
     * @param formType
     * @param formId
     */
    private void deductStockDetail(List<JSONObject> dataList, JSONArray expectOutBoundArray, int formType, Long formId,Set<String> kanbanCodeSet) {
        for (int i = 0; i < expectOutBoundArray.size(); i++) {
            JSONObject expectOutBoundJson = expectOutBoundArray.getJSONObject(i);
            BigDecimal expectQuantity = expectOutBoundJson.getBigDecimal("quantity");
            BigDecimal expectSubQuantity = expectOutBoundJson.getBigDecimal("subQuantity");
            String batchNumber = expectOutBoundJson.getString("batch_number");
            String kanbancode = expectOutBoundJson.getString("kanbancode");
            Long formSubId = expectOutBoundJson.getLong("form_sub_id");
            String uniqueCode = expectOutBoundJson.getString("uniqueCode");
            String materialBCode = expectOutBoundJson.getString("material_bcode");
            JSONArray kanbanInfos = expectOutBoundJson.getJSONArray("kanbanInfo");
            if (StrUtil.isNotBlank(kanbancode)) {
                JSONObject actualOutBoundJson = dataList.stream().filter(d -> kanbancode.equals(d.getString("kanbancode"))).findFirst().get();
                if (actualOutBoundJson.getBigDecimal("available_quantity").compareTo(BigDecimal.ONE) < 0) {
                    throwException("看板号" + kanbancode + "已出库");
                }
                // 有看板，默认扣减一条记录, 后期这个需求也不存在了
                BigDecimal subQuantity = actualOutBoundJson.getBigDecimal("available_subQuantity");
                actualOutBoundJson.put("available_quantity", BigDecimal.ZERO);
                actualOutBoundJson.put("available_subQuantity", BigDecimal.ZERO);
                //需要更新看板为空闲状态的看板集合
                kanbanCodeSet.add(kanbancode);
                addOutBoundRecordToList(dataList, formType, formId, formSubId, actualOutBoundJson,
                        expectQuantity, subQuantity);
            } else if (kanbanInfos != null && kanbanInfos.size() > 0) {
                dealKanBanOutBandData(dataList, formType, formId, kanbanInfos, expectQuantity, formSubId, materialBCode,kanbanCodeSet);
            } else if (StrUtil.isNotBlank(batchNumber)) {
                // 有批次号，要按批次号扣减
                List<JSONObject> sameBatchActualList = filter(dataList,
                        (JSONObject d) -> batchNumber.equals(d.getString("batch_number")) && d.getBigDecimal("available_quantity").compareTo(BigDecimal.ZERO) > 0);
                deductQuantity(formType, formId, formSubId, expectQuantity, BigDecimal.ZERO, sameBatchActualList, dataList,kanbanCodeSet);
            } else if (StrUtil.isNotBlank(materialBCode)) {
                List<JSONObject> actualList = filter(dataList,
                        (JSONObject d) -> d.getBigDecimal("available_quantity").compareTo(BigDecimal.ZERO) > 0 && d.getString("material_bcode").equalsIgnoreCase(materialBCode));
                deductQuantity(formType, formId, formSubId, expectQuantity, BigDecimal.ZERO, actualList, dataList,kanbanCodeSet);
            } else {
                List<JSONObject> actualList = filter(dataList,
                        (JSONObject d) -> d.getBigDecimal("available_quantity").compareTo(BigDecimal.ZERO) > 0);
                deductQuantity(formType, formId, formSubId, expectQuantity, BigDecimal.ZERO, actualList, dataList,kanbanCodeSet);
            }
        }
    }

    /**
     * 处理看板扣减库存明细
     * @param dataList
     * @param formType
     * @param formId
     * @param kanbanInfos
     * @param expectQuantity
     * @param formSubId
     * @param materialBCode
     */
    private void dealKanBanOutBandData(List<JSONObject> dataList, int formType, Long formId, JSONArray kanbanInfos, BigDecimal expectQuantity, Long formSubId, String materialBCode,Set<String> kanbanCodeSet) {
        List<JSONObject> kanbanInfoList = JSONUtil.parseArray(kanbanInfos).toList(JSONObject.class);
        List<String> kanbanCodes = kanbanInfoList.stream().map(kanbanInfo -> kanbanInfo.getString("kanBanCode")).collect(Collectors.toList());
        for (String code : kanbanCodes) {
            expectQuantity = buildKanBanOutBandData(dataList, formType, formId, code, expectQuantity, formSubId,kanbanCodeSet);
            if (expectQuantity.compareTo(BigDecimal.ZERO) == 0) {
                break;
            }
        }

        if (expectQuantity.compareTo(BigDecimal.ZERO) > 0) {
            List<JSONObject> actualList = filter(dataList,
                    (JSONObject d) -> d.getBigDecimal("available_quantity").compareTo(BigDecimal.ZERO) > 0 && d.getString("material_bcode").equalsIgnoreCase(materialBCode) );
            deductQuantity(formType, formId, formSubId, expectQuantity, BigDecimal.ZERO, actualList, dataList,kanbanCodeSet);
        }
    }

    /**
     * 构建看版出库数据
     * @param dataList
     * @param formType
     * @param formId
     * @param kanbancode
     * @param expectQuantity
     * @param formSubId
     * @return
     */
    private BigDecimal buildKanBanOutBandData(List<JSONObject> dataList, int formType, Long formId, String kanbancode, BigDecimal expectQuantity, Long formSubId,Set<String> kanbanCodeSet) {
        JSONObject actualOutBoundJson = getActualOutBoundJson(dataList, kanbancode);
        if(actualOutBoundJson==null){
            return expectQuantity;
        }
        BigDecimal availableQuantity = actualOutBoundJson.getBigDecimal("available_quantity");
        BigDecimal quantity = actualOutBoundJson.getBigDecimal("quantity");
        BigDecimal conversionRate = actualOutBoundJson.getBigDecimal("conversionRate");
        BigDecimal subQuantity = actualOutBoundJson.getBigDecimal("subQuantity");
        BigDecimal availableSubQuantity =  actualOutBoundJson.getBigDecimal("available_subQuantity");
        BigDecimal actualQuantity;
        BigDecimal actualSubQuantity;
        if(availableQuantity.compareTo(expectQuantity) >= 0){
            // 有看板，默认扣减一条记录
            actualQuantity = expectQuantity;
            BigDecimal diffAvailableQuantity = availableQuantity.subtract(expectQuantity);
            actualOutBoundJson.put("available_quantity", diffAvailableQuantity);
            if(conversionRate !=null && conversionRate.compareTo(BigDecimal.ZERO) >0){
                availableSubQuantity = diffAvailableQuantity.divide(conversionRate,2,RoundingMode.HALF_UP);
                actualSubQuantity = expectQuantity.divide(conversionRate,2,RoundingMode.HALF_UP);
            }else if(subQuantity!=null && subQuantity.compareTo(BigDecimal.ZERO) >0){
                BigDecimal tempConversionRate =  quantity.divide(subQuantity,2,RoundingMode.HALF_UP);
                availableSubQuantity = diffAvailableQuantity.divide(tempConversionRate,2,RoundingMode.HALF_UP);
                actualSubQuantity = expectQuantity.divide(tempConversionRate,2,RoundingMode.HALF_UP);
            }else{
                availableSubQuantity = diffAvailableQuantity.divide(quantity,2,RoundingMode.HALF_UP);
                actualSubQuantity = expectQuantity.divide(quantity,2,RoundingMode.HALF_UP);
            }
            actualOutBoundJson.put("available_subQuantity", availableSubQuantity);
            if(diffAvailableQuantity.compareTo(BigDecimal.ZERO) == 0){
                //需要更新看板为空闲状态的看板集合
                kanbanCodeSet.add(kanbancode);
            }
            expectQuantity = BigDecimal.ZERO;
        }else{
            actualQuantity = availableQuantity;
            actualSubQuantity = availableSubQuantity;
            expectQuantity = expectQuantity.subtract(availableQuantity);
            actualOutBoundJson.put("available_quantity", BigDecimal.ZERO);
            actualOutBoundJson.put("available_subQuantity", BigDecimal.ZERO);
            //需要更新看板为空闲状态的看板集合
            kanbanCodeSet.add(kanbancode);
        }
        addOutBoundRecordToList(dataList, formType, formId, formSubId, actualOutBoundJson, actualQuantity, actualSubQuantity);
        return expectQuantity;
    }

    private JSONObject getActualOutBoundJson(List<JSONObject> dataList, String kanbancode) {
        JSONObject actualOutBoundJson =null;
        for (JSONObject json : dataList) {
            if(kanbancode.equalsIgnoreCase(json.getString("kanbancode")) && json.getBigDecimal("available_quantity").compareTo(BigDecimal.ZERO) > 0){
                actualOutBoundJson = json;
                break;
            }
        }
//        if(actualOutBoundJson==null){
//            throwException("看板号" + code + "不存在");
//        }
        return actualOutBoundJson;
    }

    /**
     * 将需要新增加的出库记录添加到更新列表中
     * @param dataList
     * @param formType
     * @param formId
     * @param actualOutBoundJson
     */
    private void addOutBoundRecordToList(List<JSONObject> dataList, int formType, Long formId, Long formSubId, JSONObject actualOutBoundJson, BigDecimal deductQuantity, BigDecimal deductSubQuantity) {
        JSONObject addRecord = BeanUtil.copyProperties(actualOutBoundJson, JSONObject.class);
        addRecord.put("createTime", null);
        addRecord.put("updateTime", null);
        addRecord.remove("id");
        addRecord.put("stock_type", 2);
        addRecord.put("available_quantity", BigDecimal.ZERO);
        addRecord.put("available_subQuantity", BigDecimal.ZERO);
        addRecord.put("subQuantity", deductSubQuantity.negate());
        addRecord.put("quantity", deductQuantity.negate());
        addRecord.put("form_type", formType);
        addRecord.put("form_id", formId);
        addRecord.put("form_sub_id", formSubId);
        dataList.add(addRecord);
    }


    /**
     * 过滤数据
     * @param dataList
     * @return
     */
    private List<JSONObject> filter(List<JSONObject> dataList, Predicate<JSONObject> predicate) {
        List<JSONObject> sameBatchActualList = dataList.stream()
                .filter(predicate)
                .collect(Collectors.toList());
        return sameBatchActualList;
    }

    /**
     * 扣减库存
     * @param formType 业务类型， 如：销售出库
     * @param formId 业务ID
     * @param expectQuantity 预期出库主数量
     * @param expectSubQuantity 预期出库辅数量
     * @param dataList 可以扣减的stockDetail明细数据
     */
    private void deductQuantity(int formType, Long formId, Long formSubId, BigDecimal expectQuantity, BigDecimal expectSubQuantity, List<JSONObject> dataList, List<JSONObject> resultList,Set<String> kanbanCodeSet) {
        // 从第0条开始摔扣减
        if (expectQuantity != null) {
            int index = 0;
            while (expectQuantity.compareTo(BigDecimal.ZERO) > 0) {
                BigDecimal actualQuantity;
                BigDecimal actualSubQuantity;
                JSONObject actualOutBoundJson = dataList.get(index);
                BigDecimal availableQuantity = actualOutBoundJson.getBigDecimal("available_quantity");
                BigDecimal availableSubQuantity = actualOutBoundJson.getBigDecimal("available_subQuantity");
                BigDecimal quantity = actualOutBoundJson.getBigDecimal("quantity");
                BigDecimal subQuantity = actualOutBoundJson.getBigDecimal("subQuantity");
                BigDecimal conversionRate = actualOutBoundJson.getBigDecimal("conversionRate");
                String kanbancode = actualOutBoundJson.getString("kanbancode");
                if (availableQuantity.compareTo(expectQuantity) >= 0) {
                    actualQuantity = expectQuantity;
                    actualOutBoundJson.put("available_quantity", availableQuantity.subtract(expectQuantity));
                    //actualSubQuantity = expectQuantity.divide(conversionRate, 2, RoundingMode.HALF_UP);
                    if(conversionRate !=null && conversionRate.compareTo(BigDecimal.ZERO) >0){
                        availableSubQuantity = availableQuantity.subtract(expectQuantity).divide(conversionRate,2,RoundingMode.HALF_UP);
                        actualSubQuantity = expectQuantity.divide(conversionRate,2,RoundingMode.HALF_UP);
                    }else if(subQuantity!=null && subQuantity.compareTo(BigDecimal.ZERO) >0){
                        BigDecimal tempConversionRate =  quantity.divide(subQuantity,2,RoundingMode.HALF_UP);
                        availableSubQuantity = availableQuantity.subtract(expectQuantity).divide(tempConversionRate,2,RoundingMode.HALF_UP);
                        actualSubQuantity = expectQuantity.divide(tempConversionRate,2,RoundingMode.HALF_UP);
                    }else{
                        availableSubQuantity = availableQuantity.subtract(expectQuantity).divide(quantity,2,RoundingMode.HALF_UP);
                        actualSubQuantity = expectQuantity.divide(quantity,2,RoundingMode.HALF_UP);
                    }
                    actualOutBoundJson.put("available_subQuantity", availableSubQuantity);
                    BigDecimal subtract = availableQuantity.subtract(expectQuantity);
                    if(StrUtil.isNotBlank(kanbancode) && subtract.compareTo(BigDecimal.ZERO) == 0){
                        //需要更新看板为空闲状态的看板集合
                        kanbanCodeSet.add(kanbancode);
                    }
                    expectQuantity = BigDecimal.ZERO;
                } else {
                    actualQuantity = availableQuantity;
                    actualSubQuantity = availableSubQuantity;
                    expectQuantity = expectQuantity.subtract(availableQuantity);
                    actualOutBoundJson.put("available_quantity", BigDecimal.ZERO);
                    actualOutBoundJson.put("available_subQuantity", BigDecimal.ZERO);
                    if(StrUtil.isNotBlank(kanbancode)){
                        //需要更新看板为空闲状态的看板集合
                        kanbanCodeSet.add(kanbancode);
                    }
                    index++;
                }
                addOutBoundRecordToList(resultList, formType, formId, formSubId, actualOutBoundJson, actualQuantity, actualSubQuantity);
            }
        } else if (expectSubQuantity != null) {
            int index = 0;
            while (expectSubQuantity.compareTo(BigDecimal.ZERO) > 0) {
                BigDecimal actualQuantity;
                BigDecimal actualSubQuantity;
                JSONObject actualOutBoundJson = dataList.get(index);
                BigDecimal availableQuantity = actualOutBoundJson.getBigDecimal("available_quantity");
                BigDecimal availableSubQuantity = actualOutBoundJson.getBigDecimal("available_subQuantity");
                BigDecimal conversionRate = actualOutBoundJson.getBigDecimal("conversionRate");
                String kanbancode = actualOutBoundJson.getString("kanbancode");
                if (availableSubQuantity.compareTo(expectSubQuantity) >= 0) {
                    actualSubQuantity = expectSubQuantity;
                    actualQuantity = expectSubQuantity.multiply(conversionRate).setScale(2, RoundingMode.HALF_UP);
                    BigDecimal tempAvailableQuantity = availableQuantity.subtract(actualQuantity);
                    // 如果小于1件，要全部出库
                    if (availableQuantity.compareTo(BigDecimal.ONE) <= 0 || tempAvailableQuantity.compareTo(BigDecimal.ONE) <= 0) {
                        actualOutBoundJson.put("available_subQuantity", BigDecimal.ZERO);
                        actualOutBoundJson.put("available_quantity", BigDecimal.ZERO);
                        if(StrUtil.isNotBlank(kanbancode)){
                            //需要更新看板为空闲状态的看板集合
                            kanbanCodeSet.add(kanbancode);
                        }
                    } else {
                        actualOutBoundJson.put("available_subQuantity", availableSubQuantity.subtract(actualSubQuantity));
                        actualOutBoundJson.put("available_quantity", tempAvailableQuantity);
                        BigDecimal subtract = availableSubQuantity.subtract(actualSubQuantity);
                        if(StrUtil.isNotBlank(kanbancode) && subtract.compareTo(BigDecimal.ZERO) == 0){
                            //需要更新看板为空闲状态的看板集合
                            kanbanCodeSet.add(kanbancode);
                        }
                    }
                    expectSubQuantity = BigDecimal.ZERO;
                } else {
                    actualQuantity = availableQuantity;
                    actualSubQuantity = availableSubQuantity;
                    expectSubQuantity = expectSubQuantity.subtract(availableSubQuantity);
                    actualOutBoundJson.put("available_quantity", BigDecimal.ZERO);
                    actualOutBoundJson.put("available_subQuantity", BigDecimal.ZERO);
                    if(StrUtil.isNotBlank(kanbancode)){
                        //需要更新看板为空闲状态的看板集合
                        kanbanCodeSet.add(kanbancode);
                    }
                    index++;
                }
                addOutBoundRecordToList(resultList, formType, formId, formSubId, actualOutBoundJson, actualQuantity, actualSubQuantity);
            }
        }

    }

    /**
     * 校验库存是否足够
     * @param availableStockDetailList
     */
    private void predicateStockQuantity(List<JSONObject> availableStockDetailList) {
        if (availableStockDetailList.stream().anyMatch(d->!d.getBooleanValue("isEnough"))) {
            // 有库存不足的情况，不能出库
            String notEnough = availableStockDetailList.stream().filter(d -> !d.getBooleanValue("isEnough"))
                    .map(d -> d.getString("material_bcode")).collect(Collectors.joining(","));
            throwException("背番号[" + notEnough + "]库存不足");
        }
    }


    /**
     * 获取唯一一条库存数据
     * @param binId
     * @param batchNumber
     * @param materialBcode
     * @return
     */
    // private JSONObject fetchOneStock(Long binId, String batchNumber, String materialBcode) {
    //     String className = "stock";
    //     String condition = "material_bcode = '" + materialBcode + "' ";
    //     if (binId != null) {
    //         condition += " and bin_id = " + binId;
    //     }
    //     if (StrUtil.isNotBlank(batchNumber)) {
    //         condition +=" and batch_number = '" + batchNumber + "' ";
    //     }
    //
    //     List<JSONObject> dataList = logicInstanceDslService.queryWithoutHier(condition, className, null);
    //     if (CollectionUtil.isEmpty(dataList)) {
    //         throw new BusinessException(StatusCode.ERROR_DATA_ERROR.getCode(), "背番号["+materialBcode+"]找不到该批次");
    //     }
    //     if (dataList.size() != 1) {
    //         throw new BusinessException(StatusCode.ERROR_DATA_ERROR.getCode(), "库存有误");
    //     }
    //     return dataList.get(0);
    // }

    /**
     * 在入库时，获取唯一一条库存数据，
     * @param batchNumber
     * @param materialBcode
     * @return 如果没有，返回空
     */
    private JSONObject fetchInBoundOneStock(Long binId, String batchNumber, String materialBcode) {
        String className = "stock";
        String condition = "material_bcode = '" + materialBcode + "' ";
        condition +=" and batch_number = '" + batchNumber + "' ";
        condition += " and bin_id = " + binId;
        List<JSONObject> dataList = logicInstanceDslService.queryWithoutHier(condition, className, null);
        if (CollectionUtil.isEmpty(dataList)) {
            return null;
        }
        if (dataList.size() > 1) {
            throwException("库存有误");
        }
        return dataList.get(0);
    }

    /**
     * 获取唯一物料数据
     * @param materialBcode
     * @return
     */
    private JSONObject fetchOneMaterial(String materialBcode) {
        String className = "material";
        String condition = "bcode = '" + materialBcode + "' ";
        List<JSONObject> dataList= consumeInstanceDslService.query(condition, className, null);
        if (dataList.size() != 1) {
            throwException("背番号["+materialBcode+"]有误");
        }
        return dataList.get(0);
    }

    /**
     * 获取唯一库存明细
     * @param uniqueCode
     * @return
     */
    private JSONObject fetchOneStockDetailByUniqueCode(String uniqueCode) {
        String className = "stockDetail";
        String condition = "uniqueCode = '" + uniqueCode + "'";
        List<JSONObject> dataList= logicInstanceDslService.queryWithoutHier(condition, className, "id;desc");
        if (CollectionUtil.isNotEmpty(dataList)) {
            return dataList.get(0);
        }
        return null;
    }

    /**
     * 获取唯一库存明细
     * @param materialBcode
     * @return
     */
    private JSONObject fetchOneStockDetail(String materialBcode, String batchNumber, Long binId) {
        String className = "stockDetail";
        String condition = "stock_type = 1 and material_bcode = '" + materialBcode + "' " + " and batch_number = '"+ batchNumber +"'";
        // 过滤废品库的数据
        Long wasterBinId = fetchWasterBinId();
        if (wasterBinId != null) {
            condition += " and bin_id <> " + wasterBinId;
        }
        // 所有批次号的转换率应该都是一样的，所以
        // if (binId != null) {
        //     condition += "and bin_id = " + binId;
        // }
        List<JSONObject> dataList= logicInstanceDslService.queryWithoutHier(condition, className, null);
        if (CollectionUtil.isEmpty(dataList)) {
            throwException("背番号["+materialBcode+"]找不到该批次");
        }
        return dataList.get(0);
    }

    /**
     * 获取唯一库存明细
     * @param materialBcode
     * @return
     */
    private JSONObject fetchOneStockDetailOrNull(String materialBcode, String batchNumber, Long binId) {
        String className = "stockDetail";
        String condition = "material_bcode = '" + materialBcode + "' " + " and batch_number = '"+ batchNumber +"' and bin_id = " + binId;
        List<JSONObject> dataList= logicInstanceDslService.queryWithoutHier(condition, className, null);
        if (CollectionUtil.isNotEmpty(dataList)) {
            return dataList.get(0);
        } else {
            return null;
        }
    }

    /**
     * 获取唯一库位
     * @param binId
     * @return
     */
    private JSONObject fetchOneBin(Long binId) {
        JSONObject binJson = hierInstanceDslService.queryById(binId);
        return binJson;
    }

    /**
     * 校验出库数据合法性
     * @param dataJson
     * @param dataList
     */
    private void checkOutBoundData(JSONObject dataJson, List<JSONObject> dataList) {
        JSONArray paramList = dataJson.getJSONArray("data");
        for (int i = 0; i < paramList.size(); i++) {
            JSONObject param = paramList.getJSONObject(i);
            String kanbanCode = param.getString("kanbancode");
            // bin_code主要用来提示错误时用，可以不传
            String binCode = param.getString("bin_code");
            Long binId = param.getLong("bin_id");
            String materialBcode =  param.getString("material_bcode");
            String batchNumber =  param.getString("batch_number");
            BigDecimal quantity = param.getBigDecimal("quantity");
            BigDecimal subQuantity = param.getBigDecimal("subQuantity");
            if (StrUtil.isNotBlank(kanbanCode)) {
                predicateKanbanCode(dataList, kanbanCode, quantity, subQuantity);
            }
            if (binId != null) {
                if (!predicateQuantityByField(dataList, "bin_id", binId, materialBcode, quantity, subQuantity)) {
                    this.checkOutBoundMaterialRecord(materialBcode, binId, binCode);
                }
            }
            if (batchNumber != null) {
                if (!predicateQuantityByField(dataList, "batch_number", batchNumber, materialBcode, quantity, subQuantity)) {
                    throwException("背番["+ materialBcode +"]在批次号["+ batchNumber +"]中数量不足或不能出库");
                }
            }
        }
    }

    /**
     * 已领料出库记录检查
     * @param materialBcode 物料背番
     * @param binId 库位id
     * @param binCode 库位编码
     */
    private void checkOutBoundMaterialRecord(String materialBcode, Long binId, String binCode) {
        List<String> workOrderNumbers = this.getOutBoundRecords(materialBcode, binId);
        if (CollectionUtil.isEmpty(workOrderNumbers)) {
            throwException("背番[" + materialBcode + "]在库位[" + binCode + "]中数量不足或不能出库");
        } else {
            String workOrderNumbersStr = workOrderNumbers.stream()
                    .map(Object::toString)
                    .collect(Collectors.joining(","));
            throwException(" 物料背番[" + materialBcode + "]已被工单[" + workOrderNumbersStr + "]领取");
        }
    }

    /**
     * 获取领料出库记录
     */
    private List<String> getOutBoundRecords(String materialBCode, Long binId) {
//        Integer stock_type = 2; // 出入库类型：1-入库，2-出库
//        String form_type = "2"; // 业务类型：2-领料出库
        String className = "stockDetail";
        String condition = "material_bcode = '" + materialBCode + "' " + " and bin_id = " + binId + " and stock_type = 2 and form_type ='2'";
        List<JSONObject> dataList = logicInstanceDslService.queryWithoutHier(condition, className, null);
        if (CollectionUtil.isEmpty(dataList)) {
            return null;
        }

        List<Long> formIds = dataList.stream().map(d -> d.getJSONObject("values").getLong("form_id")).collect(Collectors.toList());
        if (CollectionUtil.isEmpty(formIds)) {
            return null;
        }
        String ids = formIds.stream()
                .map(Object::toString)
                .collect(Collectors.joining(","));
        String materialCondition = " id in (" + ids + ")";
        List<JSONObject> materialRecords = logicInstanceDslService.queryWithoutHier(materialCondition, "materialRecord", null);
        if (CollectionUtil.isNotEmpty(materialRecords)) {
            List<String> workOrderNumbers = materialRecords.stream().map(d -> d.getJSONObject("values").getString("orderNumber")).collect(Collectors.toList());
            return workOrderNumbers;
        } else {
            return null;
        }
    }


    /**
     * 判断指定的库位是否合法
     * @param dataList
     * @param fieldNameValue
     * @param materialBcode
     * @param quantity
     * @return
     */
    private boolean predicateQuantityByField(List<JSONObject> dataList, String fieldName, Object fieldNameValue, String materialBcode, BigDecimal quantity, BigDecimal subQuantity) {
        String fieldValueStr = String.valueOf(fieldNameValue);
        BigDecimal sumQuantity = dataList.stream()
                .filter(d -> fieldValueStr.equals(String.valueOf(d.get(fieldName))) && materialBcode.equals(d.getString("material_bcode")))
                .map(d -> d.getBigDecimal("available_quantity"))
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal sumSubQuantity = dataList.stream()
                .filter(d -> fieldValueStr.equals(String.valueOf(d.get(fieldName))) && materialBcode.equals(d.getString("material_bcode")))
                .map(d -> d.getBigDecimal("available_subQuantity"))
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        boolean quantityResult = true, subQuantityResult = true;
        if (quantity != null) {
            quantityResult = quantity.compareTo(sumQuantity) <= 0;
        }
        if (subQuantity != null) {
            quantityResult = subQuantity.compareTo(sumSubQuantity) <= 0;
        }
        return  quantityResult && subQuantityResult;
    }

    /**
     * 判断看板在可出库数据中是否存在
     * @param dataList
     * @param kanbanCode
     * @return
     *
     */
    private List predicateKanbanCode(List<JSONObject> dataList, String kanbanCode, BigDecimal quantity, BigDecimal subQuantity) {
        List<JSONObject> filterList = dataList.stream()
                .filter(d -> kanbanCode.equals(d.getString("kanbancode")))
                .collect(Collectors.toList());
        BigDecimal quantitySum = filterList.stream().map(d -> d.getBigDecimal("available_quantity"))
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal subQuantitySum = filterList.stream().map(d -> d.getBigDecimal("available_subQuantity"))
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        if (quantity != null && quantity.compareTo(quantitySum) > 0) {
            throwException( "看板号[" + kanbanCode + "]不存在或不能出库");
        }
        if (subQuantity != null && subQuantity.compareTo(subQuantitySum) > 0) {
            throwException("看板号[" + kanbanCode + "]不存在或不能出库");
        }
        return filterList;
    }


    /**
     * 去除物料重复的数据
     * 如果出现同一背番号，则合并数据后再查询，否则会出现实际库存为3，当查询数据中有两条记录，都需要3个物料，返回都满足的情况
     * @param paramJsonList
     * @return
     */
    private List<JSONObject> handleDuplicate(JSONArray paramJsonList) {
        Map<String, JSONObject> paramMap = new HashMap<>();
        for (int i = 0; i < paramJsonList.size(); i++) {
            JSONObject resultJson = new JSONObject();
            JSONObject paramJson = paramJsonList.getJSONObject(i);
            String materialBcode = paramJson.getString("material_bcode");
            BigDecimal quantity = paramJson.getBigDecimal("quantity");
            // quantity = quantity == null ? BigDecimal.ZERO : quantity;
            BigDecimal subQuantity = paramJson.getBigDecimal("subQuantity");
            // subQuantity = subQuantity == null ? BigDecimal.ZERO : subQuantity;
            Long warehouseId = paramJson.getLong("warehouse_id");
            if (paramMap.containsKey(materialBcode)) {
                JSONObject originJson = paramMap.get(materialBcode);
                BigDecimal originQuantity = originJson.getBigDecimal("quantity");
                BigDecimal originSubQuantity = originJson.getBigDecimal("subQuantity");
                if (originQuantity != null && quantity != null) {
                    originJson.put("quantity", originQuantity.add(quantity));
                }
                if (originSubQuantity != null && subQuantity != null) {
                    originJson.put("subQuantity", originSubQuantity.add(subQuantity));
                }
            } else {
                resultJson.put("material_bcode", materialBcode);
                resultJson.put("quantity", quantity);
                resultJson.put("subQuantity", subQuantity);
                resultJson.put("warehouse_id", warehouseId);
                paramMap.put(materialBcode, resultJson);
            }
        }
        List<JSONObject> resultList = paramMap.values().stream().collect(Collectors.toList());
        return resultList;
    }

    /**
     * 根据material_bcode和数量，返回可以出库的具体批次号/库位/看板号等数据
     * 可以过滤掉不能出库的批次
     * @param paramJson
     * @return Map<String, Object>, 返回是否足够，及相应的数据
     */
    private JSONObject filterAvailable(JSONObject paramJson) {
        List<JSONObject> resultList = new ArrayList<>();
        BigDecimal expectQuantity = paramJson.getBigDecimal("quantity");
        BigDecimal expectSubQuantity = paramJson.getBigDecimal("subQuantity");
        String materialBcode = paramJson.getString("material_bcode");
        Long warehouseId = paramJson.getLong("warehouse_id");
        Boolean needKanbancode = paramJson.getBoolean("needKanbancode");
        String condition = paramJson.getString("condition");
        List<JSONObject> stockDetailList = queryAvailableStockDetailBy(materialBcode,condition);
        if (warehouseId != null) {
            stockDetailList = stockDetailList.stream()
                    .filter(d->warehouseId.equals(d.getJSONObject("values").getLong("warehouse_id")))
                    .collect(Collectors.toList());
        }
        if (needKanbancode != null && needKanbancode) {
            stockDetailList = stockDetailList.stream()
                    .filter(d->StrUtil.isNotBlank(d.getJSONObject("values").getString("kanbancode")))
                    .collect(Collectors.toList());
        }
        JSONObject actualJson = addDeductDetailToResult(resultList, expectQuantity, expectSubQuantity, stockDetailList);
        JSONObject resultMap = new JSONObject();
        resultMap.put("material_bcode", materialBcode);
        BigDecimal actualQuantity = actualJson.getBigDecimal("actualQuantity");
        BigDecimal actualSubQuantity = actualJson.getBigDecimal("actualSubQuantity");
        if (expectQuantity != null) {
            resultMap.put("isEnough", actualQuantity.compareTo(expectQuantity) >= 0);
        } else if(expectQuantity != null) {
            resultMap.put("isEnough",  actualSubQuantity.compareTo(expectSubQuantity)>=0);
        }else{
            resultMap.put("isEnough",  false);
        }
        resultMap.put("actualQuantity", actualQuantity);
        resultMap.put("expectQuantity", expectQuantity);
        resultMap.put("actualSubQuantity", actualSubQuantity);
        resultMap.put("expectSubQuantity", expectSubQuantity);
        resultMap.put("data", resultList);
        return resultMap;
    }

    /**
     * 查询强制出库的明细数据
     * @param paramJson
     * @return
     */
    private JSONObject filterForceAvailable(JSONObject paramJson) {
        List<JSONObject> resultList = new ArrayList<>();
        BigDecimal expectQuantity = paramJson.getBigDecimal("quantity");
        BigDecimal expectSubQuantity = paramJson.getBigDecimal("subQuantity");
        String materialBcode = paramJson.getString("material_bcode");
        String batchNumber = paramJson.getString("batch_number");
        Long binId = paramJson.getLong("bin_id");
        JSONObject stockDetailJson = fetchOneStockDetail(materialBcode, batchNumber, binId);
        BigDecimal conversionRate = stockDetailJson.getJSONObject("values").getBigDecimal("conversionRate");
        if (expectQuantity == null) {
            expectQuantity = expectSubQuantity.divide(conversionRate, 2, RoundingMode.HALF_UP);
            paramJson.put("quantity", expectQuantity);
        }
        List<JSONObject> queryJsonList = queryAvailableStockDetail(materialBcode, batchNumber);
        JSONObject actualJson = addDeductDetailToResult(resultList, expectQuantity, expectSubQuantity, queryJsonList);
        JSONObject resultMap = new JSONObject();
        resultMap.put("material_bcode", materialBcode);
        BigDecimal actualQuantity = actualJson.getBigDecimal("actualQuantity");
        BigDecimal actualSubQuantity = actualJson.getBigDecimal("actualSubQuantity");
        if (expectQuantity != null) {
            resultMap.put("isEnough", actualQuantity.compareTo(expectQuantity) > 0);
        } else {
            resultMap.put("isEnough",  actualSubQuantity.compareTo(expectSubQuantity)>0);
        }
        resultMap.put("actualQuantity", actualQuantity);
        resultMap.put("expectQuantity", expectQuantity);
        resultMap.put("data", resultList);
        return resultMap;
    }

    /**
     * 从可扣减的列表中，过滤出应该扣减的数据，添加到扣减结果中
     * 由于每个批次的单位转换率不同，所以需要按批次分别计算主辅单位数量，如果提供了主，就计算辅，如果没主有辅，就计算主， 都没有就报错.
     * 但在计算数量是否足够时，只按主单位数量计算
     * @param resultList 保存结果的集合
     * @param expectQuantity 出库数量
     * @param queryJsonList 所有数据，即：需要被过滤的数据
     * @return
     */
    private JSONObject addDeductDetailToResult(List<JSONObject> resultList, BigDecimal expectQuantity, BigDecimal expectSubQuantity, List<JSONObject> queryJsonList) {
        BigDecimal actualQuantity = BigDecimal.ZERO;
        BigDecimal actualSubQuantity = BigDecimal.ZERO;
        String lastBatchNumber = "";
        for (int i = 0; i < queryJsonList.size(); i++) {
            JSONObject d = queryJsonList.get(i);
            JSONObject values = d.getJSONObject("values");
            values.put("id", d.getLong("id"));
            String materialBcode = values.getString("material_bcode");
            String currentBatchNumber = values.getString("batch_number");
            currentBatchNumber = handleSubBatchNumber(currentBatchNumber);
            BigDecimal conversionRate = values.getBigDecimal("conversionRate");
            // 如果有主单位，直接按主单位出库，辅单位数量与库存保持一致即可
            if (expectQuantity != null) {
                // 还不够需要的数量，将当前记录添加到结果中后， 继续增加后面的数据
                if (expectQuantity.compareTo(actualQuantity) > 0) {
                    actualQuantity = actualQuantity.add(values.getBigDecimal("available_quantity"));
                    actualSubQuantity = actualSubQuantity.add(values.getBigDecimal("available_subQuantity"));
                    resultList.add(values);
                    lastBatchNumber = currentBatchNumber;
                    // expectQuantity = expectQuantity.subtract(actualQuantity);
                    continue;
                } else {
                    // 需要的数量已经足够，需要判断还有没有同一批次的数据，如果有，就继续添加，如果没有，就跳出循环
                    if (currentBatchNumber.equals(lastBatchNumber)) {
                        actualQuantity = actualQuantity.add(values.getBigDecimal("available_quantity"));
                        actualSubQuantity = actualSubQuantity.add(values.getBigDecimal("available_subQuantity"));
                        resultList.add(values);
                    } else {
                        break;
                    }
                }
            } else if (expectSubQuantity != null) {
                // 主单位数量为空，但存在辅单位数量
                // 还不够需要的数量，将当前记录添加到结果中后， 继续增加后面的数据
                if (expectSubQuantity.compareTo(actualSubQuantity) > 0) {
                    actualQuantity = actualQuantity.add(values.getBigDecimal("available_quantity"));
                    actualSubQuantity = actualSubQuantity.add(values.getBigDecimal("available_subQuantity"));
                    resultList.add(values);
                    lastBatchNumber = currentBatchNumber;
                    continue;
                } else {
                    // 需要的数量已经足够，需要判断还有没有同一批次的数据，如果有，就继续添加，如果没有，就跳出循环
                    if (currentBatchNumber.equals(lastBatchNumber)) {
                        actualQuantity = actualQuantity.add(values.getBigDecimal("available_quantity"));
                        actualSubQuantity = actualSubQuantity.add(values.getBigDecimal("available_subQuantity"));
                        resultList.add(values);
                    } else {
                        break;
                    }
                }
            } else {
                throwException("背番号[" + materialBcode + "]出库数量不能为空");
            }
        }
        JSONObject resultJson = new JSONObject();
        resultJson.put("actualQuantity", actualQuantity);
        resultJson.put("actualSubQuantity", actualSubQuantity);
        return resultJson;
    }
    /**
     * 查询领料扫码物料是否为统一批次号
     * [
     *         {
     *             "material_bcode":"A31",
     *             "quantity":100
     *         }
     *     ]
     * @return
     */
    @Override
    public List<JSONObject> queryAvailableStockDetailCodeNum(JSONObject dataJson) {
        List<JSONObject> resultList = new ArrayList<>();
        List<JSONObject> paramJsonList =  handleDuplicate(dataJson.getJSONArray("data"));
//        String condition = "available_quantity > 0" ;
        for (int i = 0; i < paramJsonList.size(); i++) {
            JSONObject paramJson = paramJsonList.get(i);
            paramJson.put("needKanbancode", dataJson.getBoolean("needKanbancode"));
            paramJson.put("condition", dataJson.getString("condition"));
            JSONObject availableMap = filterAvailable(paramJson);
            resultList.add(availableMap);
        }
        return resultList;
    }
    /**
     * 根据material_bcode，查询库存结果
     * 返回所有可以出库的库存明细
     */
    private List<JSONObject> queryAvailableStockDetailBy(String materialBcode,String condition) {
        String className = "stockDetail";
        String orderBy = "batch_number,asc";
        if(StrUtil.isNotBlank(condition)){
            condition = condition + " and material_bcode = '" + materialBcode + "' and available_quantity > 0"
                    + " and stock_type = 1 and status = 1 " ;
        }else{
            condition = " material_bcode = '" + materialBcode + "' and available_quantity > 0"
                    + " and stock_type = 1 and status = 1 " ;
        }

        List<Long> unavailableWarehouseIdList = fetchUnavailableWarehouseId();
        for (int i = 0; i < unavailableWarehouseIdList.size(); i++) {
            Long unavailableWarehouseId = unavailableWarehouseIdList.get(i);
            condition += " and warehouse_id <>  " + unavailableWarehouseId;
        }

        Long wasterBinId = fetchWasterBinId();
        if (wasterBinId != null) {
            condition += " and bin_id <> " + wasterBinId;
        }
        Long defectiveBinId = fetchDefectiveBinId();
        if (defectiveBinId != null) {
            condition += " and bin_id <> " + defectiveBinId;
        }
        Long watiCheckBinId = fetchWaitCheckBinId();
        if (watiCheckBinId != null) {
            condition += " and bin_id <> " + watiCheckBinId;
        }
        return logicInstanceDslService.queryWithoutHier(condition, className, orderBy);
    }

    /**
     * 根据material_bcode，查询库存结果
     * 返回所有可以出库的库存明细
     */
    private List<JSONObject> queryAvailableStockDetail(String materialBcode, String batchNumber) {
        String className = "stockDetail";
        String orderBy = "id,asc";
        String condition = "material_bcode = '" + materialBcode + "' and available_quantity > 0" + " and batch_number = '" + batchNumber + "'";
        return logicInstanceDslService.queryWithoutHier(condition, className, orderBy);
    }

    /**
     * 入库单入库
     * 其实可以配置事件，但如果子表太多，发送的请求数据太大
     * 提供入库单Id， 查询明细，进行入库操作，并修改入库单状态
     * @param dataJson
     *             int formType = dataJson.getIntValue("formType");
     *             long formId = dataJson.getLongValue("formId");
     *             JSONArray dataArray = dataJson.getJSONArray("data");
     *             JSONObject paramJson = dataArray.getJSONObject(i);
     *             Long binId = paramJson.getLong("bin_id");
     *             String materialBcode = paramJson.getString("material_bcode");
     *             BigDecimal quantity = paramJson.getBigDecimal("quantity");
     *             BigDecimal subQuantity = paramJson.getBigDecimal("subQuantity");
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void inBoundByReceiptOrder(JSONObject dataJson) {
        Long containerId = dataJson.getLong("id");
        Integer status = dataJson.getInteger("status");
        // 只查询已扫码的记录
        String condition = "status = 1";
        List<HierInstance> hierInstanceList = hierInstanceDslService.queryChildByContainerId("logic", "receiptOrder", containerId, "3y3bbb7ryna000", condition);
        JSONArray dataArray = new JSONArray();
        hierInstanceList.forEach(hierInstance -> {
            JSONObject values = hierInstance.getValues();
            JSONObject detailData = new JSONObject();
            detailData.put("form_id", containerId);
            detailData.put("form_sub_id", hierInstance.getId());
            detailData.put("bin_id", values.getLongValue("bin_id"));
            detailData.put("material_bcode", values.getString("material_bcode"));
            detailData.put("quantity", values.getBigDecimal("quantity"));
            detailData.put("subQuantity", values.getBigDecimal("subQuantity"));
            detailData.put("batch_number", values.getString("batch_number"));
            detailData.put("uniqueCode", values.getString("uniqueCode"));
            // 0未检验，1已检验，9已作废
            detailData.put("status", status == null ? 0 : status);
            dataArray.add(detailData);
        });
        dataJson.put("data", dataArray);
        inBound(dataJson);

        // 入库时，生成了明细的批次号，回写数据
        for (int i = 0; i < dataArray.size(); i++) {
            JSONObject hierJson = dataArray.getJSONObject(i);
            HierInstance hierInstance = new HierInstance();
            hierInstance.setId(hierJson.getLong("form_sub_id"));
            JSONObject valueJson = new JSONObject();
            valueJson.put("batch_number", hierJson.getString("batch_number"));
            hierInstance.setValues(valueJson);
            hierInstanceDslService.updateById(hierInstance);
        }

        // 检查是否有可入库数据，即扫码数据
        List<HierInstance> receiptHierList = hierInstanceDslService.queryChildByContainerId("logic", "receiptOrder", containerId, "3y3bbb7ryna000", null);
        long validCount = receiptHierList.stream().filter(d -> d.getValues().getInteger("status") == 1).count();
        if (validCount < 1) {
            throwException("没有可入库数据");
        }
        // 入库明细 还要回写累计入库数量，因为在扫码的时候，已经更新了入库数量，所以只需要累加到上游单据即可，关联字段名称：form_sub_id
        // 将入库单的信息，回写到上游数据【订单】中
        List<HierInstance> detailHierList = hierInstanceDslService.queryChildByContainerId("logic", "receiptOrder", containerId, "21islklrx8u800", null);
        detailHierList.forEach(detailHierInstance->{
            JSONObject hierValues = detailHierInstance.getValues();
            Long formSubId = hierValues.getLong("form_sub_id");
            if (formSubId != null) {
                JSONObject upstreamHierInstanceJson = hierInstanceDslService.queryById(formSubId);
                JSONObject upstreamValues = upstreamHierInstanceJson.getJSONObject("values");
                BigDecimal arrivalQuantity = hierValues.getBigDecimal("arrivalQuantity");
                BigDecimal arrivalSubQuantity = hierValues.getBigDecimal("arrivalSubQuantity");
                BigDecimal upstreamArrivalQuantity= upstreamValues.getBigDecimal("arrivalQuantity") == null ? BigDecimal.ZERO : upstreamValues.getBigDecimal("arrivalQuantity");
                BigDecimal upstreamArrivalSubQuantity = upstreamValues.getBigDecimal("arrivalSubQuantity") == null ? BigDecimal.ZERO : upstreamValues.getBigDecimal("arrivalSubQuantity");
                arrivalQuantity = arrivalQuantity == null ? BigDecimal.ZERO : arrivalQuantity;
                arrivalSubQuantity = arrivalSubQuantity == null ? BigDecimal.ZERO : arrivalSubQuantity;
                upstreamValues.put("arrivalQuantity", arrivalQuantity.add(upstreamArrivalQuantity));
                upstreamValues.put("arrivalSubQuantity", arrivalSubQuantity.add(upstreamArrivalSubQuantity));
                hierInstanceDslService.updateById(JsonUtil.toJavaBean(upstreamHierInstanceJson, HierInstance.class));
            }
        });
    }

    /**
     * 销售出库时，写了form_id，如果按form_id回滚，流程应该是：
     * 1. 根据form_id查询出库或入库记录（入库还是出库，需要指定，但可能会多次出库？）
     * 2. 将查询出的出库记录作废
     * 3. 将出库记录关联的入库记录可用数量增加回去（如何增加？根据货码明细，如果一个货码，入库-出库-入库-出库-入库
     * 这时会出现5个一样的货码，但只有一个有可用数量，此时回滚的话，应该回滚哪个数据，目前只能以最近的一次为准）
     *  作废至少有三种情况：
     *          1. 入库作废，如：入库单入库
     *          2. 出库作废，如：销售出库
     *          3. 出入库同时作废，如：领料，退料，调拨
     *          注意事项：
     *          0. 查询是否已经有下游数据
     *          1. 如果只有入库，直接作废
     *          2. 如果只有出库记录，需要将出库记录作废，同时按照唯一码，找到最近一次入库记录，将数量还原
     *          3. 出入库同时作废，需要根据出库记录，找到跟自己form_id不一样的，最近一次的入库记录，进行数量还原操作
     * @param dataJson
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean stockCancel(JSONObject dataJson) {
        Long formId = dataJson.getLong("formId");
        JSONObject formInstance = logicInstanceDslService.queryJsonById(formId);
        String approveDateStr = formInstance.getJSONObject("values").getString("approveDate");
        if (StrUtil.isBlank(approveDateStr)) {
            throwException("该表单审核时间为空，无法作废");
        }

        JSONObject lastTakeStockOrder = fetchLastTakeStockOrder();
        if (lastTakeStockOrder != null) {
            String takeStockOrderDate = lastTakeStockOrder.getJSONObject("values").getString("operateDate");
            int compare = DateUtil.compare(DateUtil.parseDate(approveDateStr), DateUtil.parseDate(takeStockOrderDate));
            if (compare <= 0) {
                throwException("该记录已盘点，无法作废");
            }
        }
        List<JSONObject> stockDetailList = queryByFormId(formId);

        // 查询是否已经有下游数据
        boolean downStreamResult = predicateDownStream(stockDetailList);
        if (downStreamResult) {
            throwException("存在下游数据，无法作废库存记录");
        }
        // 开始作废
        // 1. 作废所有记录
        stockDetailList.forEach(d->d.getJSONObject("values").put("status", 9));
        logicInstanceDslService.updateByIds(BeanUtil.copyToList(stockDetailList, JSONObject.class));
        // 找出需要作废的库存记录，用于将看板还原成未使用状态
        List<JSONObject> needCancelInBoundList = stockDetailList.stream().filter(d -> d.getJSONObject("values").getInteger("stock_type") == 1).collect(Collectors.toList());
        updateKanbanToUnAvailable(needCancelInBoundList);

        // 找到出库记录
        List<JSONObject> outBoundDetailList = stockDetailList.stream().filter(d -> d.getJSONObject("values").getInteger("stock_type") == 2).collect(Collectors.toList());
        // 找到跟自己form_id不一样的，最近一次的入库记录，进行数量还原操作
        if (CollectionUtil.isNotEmpty(outBoundDetailList)) {
            List<JSONObject> inBoundDetailList = queryNeedRestoreList(outBoundDetailList);
            if (outBoundDetailList.size() != inBoundDetailList.size()) {
                throwException("库存数据有异常，无法作废");
            }
            // 进行还原操作
            restore(inBoundDetailList);
        }
        return true;
    }

    /**
     * 获取最后一次盘点的记录
     */
    private JSONObject fetchLastTakeStockOrder() {
        String condition = "isApproved = 1";
        String className = "takeStockOrder";
        String orderBy =  "id,desc";
        List<JSONObject> takeStockOrderList = logicInstanceDslService.query(condition, className, orderBy);
        if (CollectionUtil.isEmpty(takeStockOrderList)) {
            return null;
        }
        JSONObject lastTakeStockOrder = takeStockOrderList.stream()
                .max(Comparator.comparingLong(d -> d.getLong("id")))
                .get();
        return lastTakeStockOrder;
    }

    /**
     * 进行还原入库操作
     * @param inBoundDetailList
     */
    private void restore(List<JSONObject> inBoundDetailList) {
        inBoundDetailList.forEach(d->{
            JSONObject values = d.getJSONObject("values");
            values.put("available_quantity", values.getBigDecimal("quantity"));
            values.put("available_subQuantity", values.getBigDecimal("subQuantity"));
            updateKanbanToAvailable(inBoundDetailList);
        });
        logicInstanceDslService.updateByIds(inBoundDetailList);
    }

    /**
     * 查找需要还原的入库记录
     * @param stockDetailList
     */
    private List<JSONObject> queryNeedRestoreList(List<JSONObject> stockDetailList) {
        long minId = stockDetailList.stream().mapToLong(d -> d.getLong("id")).min().getAsLong();
        String uniqueCodeListStr = stockDetailList.stream().map(d -> d.getJSONObject("values").getString("uniqueCode"))
                .map(s -> "'" + s + "'").collect(Collectors.joining(","));
        String batchNumberListStr = stockDetailList.stream().map(d -> d.getJSONObject("values").getString("batch_number"))
                .map(s -> "'" + s + "'").collect(Collectors.joining(","));
        String condition = " stock_type = 1 and id < " + minId + " and uniqueCode in  (" + uniqueCodeListStr + ") " + " and batch_number in ("+ batchNumberListStr +") ";
        String className = "stockDetail";
        // 查询入库记录，可能有很多条重复的货码，所以要过滤处理
        List<JSONObject> allInBoundDetailList = logicInstanceDslService.query(condition, className);
        // 过滤出需要还原的入库记录
        List<JSONObject> inBoundDetailList = allInBoundDetailList.stream()
                .collect(Collectors.groupingBy(d -> d.getJSONObject("values").getString("uniqueCode"),
                        Collectors.maxBy(Comparator.comparingLong(d -> d.getLong("id")))))
                .values().stream()
                .map(Optional::get)
                .collect(Collectors.toList());
        return inBoundDetailList;
    }


    /**
     * 查询下游单据数量是否存在
     * @param stockDetailList
     * @return
     */
    private boolean predicateDownStream(List<JSONObject> stockDetailList) {
        if (CollectionUtil.isEmpty(stockDetailList)) {
            return true;
        }
        long maxId = stockDetailList.stream().mapToLong(d -> d.getLong("id")).max().getAsLong();
        String uniqueCodeListStr = stockDetailList.stream().map(d -> d.getJSONObject("values").getString("uniqueCode"))
                .map(s -> "'" + s + "'").collect(Collectors.joining(","));
        String condition = " id > " + maxId + " and uniqueCode in  (" + uniqueCodeListStr + ")";
        String className = "stockDetail";
        long count = logicInstanceDslService.count(condition, className);
        return count > 0;
    }


    private void throwException(String msg) {
        throw new BusinessException(StatusCode.ERROR_DATA_ERROR.getCode(), msg);
    }


    /**
     * 查询主表的出入库记录，只查询未报废的记录
     * @param formId
     * @return
     */
    private List<JSONObject> queryByFormId(Long formId) {
        String condition = " status <> 9 and form_id = " + formId;
        String className = "stockDetail";
        List<JSONObject> stockDetailList = logicInstanceDslService.query(condition, className);
        return stockDetailList;
    }

    /**
     * 根据盘点单，生成盘盈单/盘亏单
     * @param dataJson
     *
     * [{
     *          "target":{
     *             "type":"logic",
     *             "className":"PurchaseArrivalOrder",
     *             "parentId": "1rquw2z96utc00",
     *             "values":{
     *               "code":"autogen.YYYY-receiptOrder",
     *               "status":0,
     * "form_type":"采购到货单"
     *             },
     *             "hierValues":{
     *               "status":0
     *             }
     *          },
     *          "propertyMapList":[
     *            "master.id:master.form_id",
     *            "master.values.supplier_id:master.supplier_id",
     *            "master.values.supplier_name:master.supplier_name",
     *            "master.values.supplier_code:master.supplier_code",
     *            "hier.id:hier.form_sub_id",
     * "${hier.values.quantity}-${hier.values.arrivalQuantity}:hier.quantity",
     * "hier.values.quantity:hier.purchase_quantity"
     *          ],
     * "hierValueCondition": "${values.arrivalQuantity}<${values.quantity}"
     *        }]
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void createSurplusAndLoss(JSONObject dataJson) {
        // 声明盘盈单，盘亏单
        // List<String> propertyMapList = new ArrayList();
        // propertyMapList.add("master.id:master.form_id");
        // propertyMapList.add("hier.id:hier.form_sub_id");
        //
        // JSONObject surplusTargetJson = new JSONObject();
        // surplusTargetJson.put("type", "logic");
        // surplusTargetJson.put("className", "takeStockSurplusOrder");
        // surplusTargetJson.put("parentId", "5qpwxjfz0ng000");
        // JSONObject valueJson = new JSONObject();
        // valueJson.put("code", "autogen.YYYY-takeStockSurplusOrder");
        // surplusTargetJson.put("values", valueJson);
        //
        // JSONObject lossTargetJson = new JSONObject();
        // lossTargetJson.put("type", "logic");
        // lossTargetJson.put("className", "takeStockLossOrder");
        // lossTargetJson.put("parentId", "5qpwxjfz0ng000");
        // JSONObject valueJson2 = new JSONObject();
        // valueJson2.put("code", "autogen.YYYY-takeStockLossOrder");
        // lossTargetJson.put("values", valueJson2);
        //
        // long takeStockOrderId = dataJson.getLongValue("id");
        // JSONObject takeStockOrderJson = logicInstanceDslService.queryJsonById(takeStockOrderId);
        // JSONObject surplusSourceJson = new JSONObject();
        // surplusSourceJson.put("master", takeStockOrderJson);
        // JSONObject lossSourceJson = new JSONObject();
        // lossSourceJson.put("master", takeStockOrderJson);
        //
        //
        // // 获取明细数据
        // List<HierInstance> surplusHierInstanceList = new ArrayList<>();
        // List<HierInstance> lossHierInstanceList = new ArrayList<>();
        // List<HierInstance> hierInstanceList = hierInstanceDslService.queryChildByContainerId("logic", "takeStockOrder", takeStockOrderId, "5qpwxjfz0ng000", "");
        // hierInstanceList.forEach(hierInstance -> {
        //     JSONObject hierValues = hierInstance.getValues();
        //     Long binId = hierValues.getLong("bin_id");
        //     String materialBcode = hierValues.getString("material_bcode");
        //     // 实盘数量
        //     BigDecimal realQuantity = hierValues.getBigDecimal("quantity");
        //     // 系统中的库存数量
        //     BigDecimal actualQuantity = queryCount(binId, materialBcode);
        //     if (actualQuantity.compareTo(realQuantity) > 0) {
        //         // 盘亏
        //         hierInstance.getValues().put("due_quantity", actualQuantity);
        //         lossHierInstanceList.add(hierInstance);
        //     } else if (actualQuantity.compareTo(realQuantity) < 0) {
        //         // 盘盈
        //         hierInstance.getValues().put("due_quantity", actualQuantity);
        //         surplusHierInstanceList.add(hierInstance);
        //     }
        // });
        // surplusSourceJson.put("slave", surplusHierInstanceList);
        // lossSourceJson.put("slave", lossHierInstanceList);
        // if (CollectionUtil.isNotEmpty(surplusHierInstanceList)) {
        //     logicInstanceBslService.copy(Arrays.asList(surplusSourceJson), surplusTargetJson, propertyMapList);
        // }
        // if (CollectionUtil.isNotEmpty(lossHierInstanceList)) {
        //     logicInstanceBslService.copy(Arrays.asList(lossSourceJson), lossTargetJson, propertyMapList);
        // }

    }

    /**
     * 删除扫码子子表中的数据
     * 1. 查询子子表数据
     * 2. 更新子表数据
     * 3. 删除子子表数据
     * @param paramJson
     * @return 返回更新的子表数据
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public JSONObject deleteScan(JSONObject paramJson) {
        JSONObject conditionJson = paramJson.getJSONObject("condition");
        // 汇总信息
        String childParentId = conditionJson.getString("childParentId");
        // 子子表明细信息 detailParentId
        String detailParentId = conditionJson.getString("detailParentId");
        // 子子表数据ID
        JSONObject detailData = conditionJson.getJSONObject("detailData");
        Long detailId = detailData.getLong("id");
        JSONObject detailInstanceJson = hierInstanceDslService.queryById(detailId);
        Long containerId = detailInstanceJson.getLong("containerId");
        String parentId = detailInstanceJson.getString("parentId");
        JSONObject detailValues = detailInstanceJson.getJSONObject("values");
        String materialBcode = detailValues.getString("material_bcode");
        Long binId = detailValues.getLong("bin_id");
        Long targetBinId = detailValues.getLong("target_bin_id");
        // 子表汇总数据
        HierInstance childInstance = fetchHier(containerId, childParentId, materialBcode, binId, targetBinId);
        JSONObject childValues = childInstance.getValues();
        BigDecimal newSubQuantity = childValues.getBigDecimal("subQuantity").subtract(detailValues.getBigDecimal("subQuantity"));
        BigDecimal newQuantity = childValues.getBigDecimal("quantity").subtract(detailValues.getBigDecimal("quantity"));
        childValues.put("subQuantity", newSubQuantity);
        childValues.put("quantity", newQuantity);
        hierInstanceDslService.updateById(childInstance);

        // if (newQuantity.compareTo(BigDecimal.ZERO) > 0 || newSubQuantity.compareTo(BigDecimal.ZERO) > 0) {
        //     hierInstanceDslService.updateById(childInstance);
        // } else {
        //     log.info("数量为0，删除子表数据");
        //     // 因为前端组件刷新问题，先不删除
        //     hierInstanceDslService.deleteById(childInstance.getId());
        // }
        // 删除明细数据
        hierInstanceDslService.deleteById(detailId);
        JSONObject result = new JSONObject();
        result.put("childData", hierInstanceDslService.convertValuesByProperty(childInstance));
        return result;
    }


    /**
     * 调拨扫码时，增加或更新子表数据，即汇总信息数据,
     * 库存盘点扫码时，增加或更新子表数据，即汇总信息数据,
     * 根据源库位与背番，可以确定一条数据汇总数据
     * @param paramJson 包含唯一码，目标库位
     *                  主表ID： "containerId",
     *                  扫码可得："uniqueCode","target_warehouse_id","target_warehouse_code","target_bin_id","target_bin_code"
     * @return 增加或更新后的数据
     * 组件参数：
     *
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public JSONObject stockTransferScan(JSONObject paramJson) {
        JSONObject conditionJson = paramJson.getJSONObject("condition");
        // JSONObject materialInfo = conditionJson.getJSONObject("materialInfo");
        String uniqueCode = conditionJson.getString("uniqueCode");
        JSONObject targetBinInfo = conditionJson.getJSONObject("binInfo");
        Long containerId = conditionJson.getLong("id");
        String className = conditionJson.getString("className");
        Long targetWarehouseId = targetBinInfo.getLong("warehouse_id");
        String targetWarehouseCode = targetBinInfo.getString("warehouse_code");
        Long targetBinId = targetBinInfo.getLong("id");
        String targetBinCode = targetBinInfo.getString("bin_code");
        // String uniqueCode = materialInfo.getString("uniqueCode");
        // 汇总信息
        String gatherParentId = conditionJson.getString("childParentId");
        // 子子表明细信息 detailParentId
        String detailParentId = conditionJson.getString("detailParentId");

        // 查询子子表是否有数据
        String detailCondition = "uniqueCode = '" + uniqueCode + "'";
        List<HierInstance> detailList = hierInstanceDslService.queryChildByContainerId(ModelType.Logic.getCode(), className, containerId, detailParentId, detailCondition);
        if (CollectionUtil.isNotEmpty(detailList)) {
            throwException("扫码重复");
        }
        JSONObject stockDetailJson = fetchAvailableByUniqueCode(uniqueCode);
        if (stockDetailJson == null && "takeStockOrder".equals(className)) {
            // stockDetailJson = fetchLastByUniqueCode(uniqueCode);
            if (stockDetailJson == null) {
                throwException("该物料没有出入库记录");
            }
            // 如果查到的是出库记录，需要将有效数量，更新为数量，因为出库记录的有效数量为0
            // JSONObject values = stockDetailJson.getJSONObject("values");
            // values.put("available_quantity", values.getBigDecimal("quantity"));
            // values.put("available_subQuantity", values.getBigDecimal("subQuantity"));
            // values.put("warehouse_id", targetWarehouseId);
            // values.put("warehouse_code", targetWarehouseCode);
            // values.put("bin_id", targetBinId);
            // values.put("bin_code", targetBinCode);
        }

        JSONObject stockDetailValue = stockDetailJson.getJSONObject("values");
        String materialBcode = stockDetailValue.getString("material_bcode");
        Long binId = stockDetailValue.getLong("bin_id");
        if (targetWarehouseId != null) {
            stockDetailValue.put("target_warehouse_id", targetWarehouseId);
            stockDetailValue.put("target_warehouse_code", targetWarehouseCode);
            stockDetailValue.put("target_bin_id", targetBinId);
            stockDetailValue.put("target_bin_code", targetBinCode);
        }

        // 添加子子表记录
        JSONObject detailInstanceJson = buildStockTransfer(containerId, detailParentId, stockDetailValue);
        JSONObject values = detailInstanceJson.getJSONObject("values");
        values.put("uniqueCode", uniqueCode);
        HierInstance detailInstance = hierInstanceDslService.addChild(detailInstanceJson);

        // 查询子表中是否有数据
        HierInstance gatherInstance = fetchHier(containerId, gatherParentId, materialBcode, binId, targetBinId);
        // 新增或更新数据
        if (gatherInstance != null) {
            BigDecimal newSubQuantity = gatherInstance.getValues().getBigDecimal("subQuantity").add(stockDetailValue.getBigDecimal("subQuantity"));
            BigDecimal newQuantity = gatherInstance.getValues().getBigDecimal("quantity").add(stockDetailValue.getBigDecimal("quantity"));
            gatherInstance.getValues().put("subQuantity", newSubQuantity);
            gatherInstance.getValues().put("quantity", newQuantity);
            hierInstanceDslService.updateById(gatherInstance);
        } else {
            JSONObject gatherInstanceJson = buildStockTransfer(containerId, gatherParentId, stockDetailValue);
            gatherInstance = hierInstanceDslService.addChild(gatherInstanceJson);
        }

        JSONObject result = new JSONObject();
        result.put("childData", hierInstanceDslService.convertValuesByProperty(gatherInstance));
        result.put("detailData", hierInstanceDslService.convertValuesByProperty(detailInstance));
        return result;
    }

    /**
     * 构建调拨库存汇总数据
     * @param containerId
     * @param gatherParentId
     * @param stockDetailValue
     * @return
     */
    private JSONObject buildStockTransfer(Long containerId, String gatherParentId, JSONObject stockDetailValue) {
        JSONObject gatherInstanceJson = initInstance(containerId, gatherParentId, stockDetailValue);
        JSONObject values = gatherInstanceJson.getJSONObject("values");
        values.put("bin_id", stockDetailValue.getLong("bin_id"));
        values.put("bin_code", stockDetailValue.getString("bin_code"));
        values.put("batch_number", stockDetailValue.getString("batch_number"));
        values.put("uniqueCode", stockDetailValue.getString("uniqueCode"));
        if (stockDetailValue.getLong("target_warehouse_id") != null) {
            values.put("target_warehouse_id", stockDetailValue.getLong("target_warehouse_id"));
            values.put("target_warehouse_code", stockDetailValue.getString("target_warehouse_code"));
            values.put("target_bin_id", stockDetailValue.getLong("target_bin_id"));
            values.put("target_bin_code", stockDetailValue.getString("target_bin_code"));
        }
        gatherInstanceJson.put("values", values);
        return gatherInstanceJson;
    }

    /**
     * 初始化实例信息，主要用来初始化物料数据
     * @param containerId
     * @param gatherParentId
     * @param stockDetailValue
     * @return
     */
    private JSONObject initInstance(Long containerId, String gatherParentId, JSONObject stockDetailValue) {
        JSONObject gatherInstanceJson = new JSONObject();
        gatherInstanceJson.put("containerId", containerId);
        gatherInstanceJson.put("containerModel", "logic");
        gatherInstanceJson.put("parentId", gatherParentId);
        JSONObject values = new JSONObject();
        values.put("quantity", stockDetailValue.getBigDecimal("available_quantity"));
        values.put("subQuantity", stockDetailValue.getBigDecimal("available_subQuantity"));
        values.put("material_pcode", stockDetailValue.getString("material_pcode"));
        values.put("material_bcode", stockDetailValue.getString("material_bcode"));
        values.put("material_model", stockDetailValue.getString("material_model"));
        values.put("material_spec", stockDetailValue.getString("material_spec"));
        values.put("material_unit", stockDetailValue.getString("material_unit"));
        values.put("material_subUnit", stockDetailValue.getString("material_subUnit"));
        values.put("material_id", stockDetailValue.getLong("material_id"));
        values.put("warehouse_id", stockDetailValue.getLong("warehouse_id"));
        values.put("warehouse_code", stockDetailValue.getString("warehouse_code"));
        gatherInstanceJson.put("values", values);
        return gatherInstanceJson;
    }

    /**
     * 查询唯一子表数据
     * @param containerId
     * @param gatherParentId
     * @param materialBcode
     * @param binId
     */
    private HierInstance fetchHier(Long containerId, String gatherParentId, String materialBcode, Long binId, Long targetBinId) {
        List<HierInstance> hierInstanceList = hierInstanceDslService.fetchByParentNodeId("logic", containerId, gatherParentId);
        HierInstance hierInstance = hierInstanceDslService.filterNode(hierInstanceList,
                (HierInstance hi) -> {
                    JSONObject values = hi.getValues();
                    return values.getString("material_bcode").equals(materialBcode) &&
                            values.getLong("bin_id").equals(binId) &&
                            (targetBinId == null || values.getLong("target_bin_id").equals(targetBinId));
                });
        return hierInstance;
    }


    /**
     * 库存盘点
     * 1. 参数中包含盘点时的扫码明细，及盘点仓库
     * 2. 查询出盘点仓库的所有明细数据（只查询有效的）
     * 3. 与扫码明细根据唯一码进行比较
     * 4. 系统中多出的明细，属于盘亏出库数据
     * 5. 扫码中多出的明细，属于盘盈入库数据
     * @param dataJson
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void takeStock(JSONObject dataJson) {
        Long containerId = dataJson.getLong("containerId");
        JSONObject takeStockOrderJson = logicInstanceDslService.queryJsonById(containerId);
        // 更新盘点日期
        takeStockOrderJson.getJSONObject("values").put("operateDate", LocalDateTimeUtil.now());
        logicInstanceDslService.updateById(BeanUtil.copyProperties(takeStockOrderJson, JSONObject.class));
        // 盘点单的扫码明细，每条代表一货一码中的一条数据,包括：唯一码，库位，主辅数量
        List<HierInstance> scanList = hierInstanceDslService.queryChildByContainerId("logic", "takeStockOrder", containerId, "6ryf8tgpi2c00", null);
        if (CollectionUtil.isEmpty(scanList)) {
            return;
        }
        Map<String, JSONObject> scanMap = scanList.stream()
                .collect(Collectors.toMap(d -> d.getValues().getString("uniqueCode"), d -> JsonUtil.toJSONObject(d)));
        List<String> scanUniqueCodeList = scanMap.keySet().stream().collect(Collectors.toList());
        // 查询出扫码数据中，在系统库存中仍然存在的数据
        Map<String, Long> inSystemStockMap = fetchInBoundDetailIdByUniqueCodeList(scanUniqueCodeList);
        List<Long> scanInSystemDetailIdList =  inSystemStockMap.values().stream().collect(Collectors.toList());
        // 查询仓库库存明细列表，表示预期库存
        Long warehouseId = takeStockOrderJson.getJSONObject("values").getLong("warehouse_id");
        List<Long> expectDetailIdList = fetchStockDetailIdList(warehouseId);

        // 对比数据，找出扫码时扫了物料，但系统库存中不存在的数据，即盘盈数据
        List<String> surplusUniqueCodeList = scanUniqueCodeList.stream().filter(d -> !inSystemStockMap.containsKey(d)).collect(Collectors.toList());
        // 找出这些数据最后一条出库的记录，拿到主辅数量，物料信息等进行入库操作。
        if (CollectionUtil.isNotEmpty(surplusUniqueCodeList)) {
            Map<String, JSONObject> surplusStockMap = fetchOutBoundDetailListByUniqueCodeList(surplusUniqueCodeList);
            // 如果扫码信息在库存中没存在过，就抛异常
            if (surplusStockMap.size() != surplusUniqueCodeList.size()) {
                String errorUniqueCode = surplusUniqueCodeList.stream().filter(d -> !surplusStockMap.containsKey(d)).collect(Collectors.joining(","));
                throwException("扫码数据没有入库记录,唯一码为: " +  errorUniqueCode);
            }
            // 盘盈数据
            List<JSONObject> inBoundDataList = new ArrayList<>();
            surplusStockMap.forEach((uniqueCode, outStockDetail) -> {
                JSONObject scanDetailJson = scanMap.get(uniqueCode).getJSONObject("values");
                JSONObject inBoundDetail =  outStockDetail.getJSONObject("values");
                inBoundDetail.put("stock_type", 1);
                inBoundDetail.put("warehouse_id", scanDetailJson.get("target_warehouse_id"));
                inBoundDetail.put("warehouse_code", scanDetailJson.get("target_warehouse_code"));
                inBoundDetail.put("bin_id", scanDetailJson.get("target_bin_id"));
                inBoundDetail.put("bin_code", scanDetailJson.get("target_bin_code"));
                inBoundDetail.put("available_quantity", inBoundDetail.getBigDecimal("quantity"));
                inBoundDetail.put("available_subQuantity", inBoundDetail.getBigDecimal("subQuantity"));
                inBoundDetail.remove("form_sub_id");
                inBoundDataList.add(inBoundDetail);
            });
            // 盘盈入库(14)
            JSONObject inBoundDataJson = new JSONObject();
            inBoundDataJson.put("formType", 14);
            inBoundDataJson.put("formId", containerId);
            inBoundDataJson.put("data", JsonUtil.toJSONArray(inBoundDataList));
            inBound(inBoundDataJson);
        }

        // 盘亏数据
        List<Long> lossIdList = CollectionUtil.subtractToList(expectDetailIdList, scanInSystemDetailIdList);
        // 盘亏出库(15)
        if (CollectionUtil.isNotEmpty(lossIdList)) {
            outBoundByIdList(lossIdList, containerId, 15);
        }
    }

    /**
     * 倒库单倒库
     * formType: 倒库入库 45, 倒库出库 46
     * @param dataJson
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void stockExchange(JSONObject dataJson) {
        Long formId = dataJson.getLong("formId");
        Long firstBinId = dataJson.getLong("firstBinId");
        Long secondBinId = dataJson.getLong("secondBinId");

        String condition = "available_quantity > 0 and stock_type = 1 and status = 1 " ;;
        String condition1 = " and bin_id = " + firstBinId;
        String condition2 = " and bin_id = " + secondBinId;
        String className = "stockDetail";
        List<JSONObject> firstStock = logicInstanceDslService.query(condition + condition1, className, "");
        List<JSONObject> secondStock = logicInstanceDslService.query(condition + condition2, className, "");

        // 交换库存
        exchange(formId, firstStock, secondBinId);
        exchange(formId, secondStock, firstBinId);
    }

    /**
     * 从firstStock出库， 并入到 指定到库位 中
     * @param formId
     * @param firstStock
     * @param inBinId
     */
    private void exchange(Long formId, List<JSONObject> firstStock, Long inBinId) {
        List<Long> firstStockDetailIdList = firstStock.stream().map(d -> d.getLong("id")).collect(Collectors.toList());
        outBoundByIdList(firstStockDetailIdList, formId, 46);
        JSONObject bin = hierInstanceDslService.queryById(inBinId);
        JSONObject firstValue = bin.getJSONObject("values");
        Long inWarehouseId = firstValue.getLong("warehouse_id");
        String inWarehouseCode = firstValue.getString("warehouse_code");
        String inBinCode = firstValue.getString("bin_code");
        inBoundByStockDetail(firstStock, formId, 45, inWarehouseCode, inWarehouseId, inBinCode, inBinId);
    }


    /**
     * 库存调拨
     * 1. 参数中包含扫码明细
     * 2. 根据明细进行出库操作，类型为：调拨出库 21
     * 3. 根据明细进行入库操作，类型为：调拨入库 20
     * @param dataJson
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void stockTransfer(JSONObject dataJson) {
        Long containerId = dataJson.getLong("containerId");
        // JSONObject stockTransferJson = logicInstanceDslService.queryJsonById(containerId);
        // 扫码明细，每条代表一货一码中的一条数据,包括：唯一码，源库位，目标库位，主辅数量
        List<HierInstance> scanList = hierInstanceDslService.queryChildByContainerId(ModelType.Logic.getCode(), "stockTransferOrder", containerId, "3y298hufany000", null);
        // 调拨出库
        List<JSONObject>  outBoundDetailList = transferOutBound(containerId, scanList);
        // 调拨入库，应该根据出库记录去进行入库操作
        transferInBound(containerId, outBoundDetailList, scanList);
    }

    /**
     * 调拨出库
     * @param containerId
     * @param scanList
     */
    private List<JSONObject> transferOutBound(Long containerId, List<HierInstance> scanList) {
        JSONObject outBoundJson = new JSONObject();
        outBoundJson.put("formId", containerId);
        outBoundJson.put("formType", 21);
        List<String> uniqueCodeList = scanList.stream().map(d -> d.getValues().getString("uniqueCode")).collect(Collectors.toList());
        outBoundJson.put("uniqueCodeList", uniqueCodeList);
        return outBoundByUniqueCodeList(outBoundJson);
    }

    /**
     * 调拨入库
     * @param containerId
     * @param scanList
     */
    private void transferInBound(Long containerId, List<JSONObject>  outBoundDetailList, List<HierInstance> scanList) {
        Map<String, JSONObject> outDetailMap = outBoundDetailList.stream().collect(Collectors.toMap(d -> d.getJSONObject("values").getString("uniqueCode"), d -> d.getJSONObject("values"), (k1, k2) -> k1));
        List<JSONObject> inBoundDetailList = new ArrayList<>();
        scanList.forEach(scan->{
            JSONObject scanValues = scan.getValues();
            String uniqueCode = scanValues.getString("uniqueCode");
            JSONObject outDetailJson = outDetailMap.get(uniqueCode);
            String batchNumber = outDetailJson.getString("batch_number");
            JSONObject paramJson = new JSONObject();

            //数量相关的数据，无法扫码获得，所以通过出库数据获取
            paramJson.put("quantity", outDetailJson.getBigDecimal("quantity"));
            paramJson.put("subQuantity", outDetailJson.getBigDecimal("subQuantity"));
            paramJson.put("conversionRate", outDetailJson.getBigDecimal("subQuantity").divide(outDetailJson.getBigDecimal("quantity"), 2, RoundingMode.HALF_UP));
            paramJson.put("available_quantity", outDetailJson.getBigDecimal("quantity"));
            paramJson.put("available_subQuantity", outDetailJson.getBigDecimal("subQuantity"));
            paramJson.put("uniqueCode", uniqueCode);
            paramJson.put("status", outDetailJson.get("status"));
            JSONObject binJsonValues = new JSONObject();
            binJsonValues.put("warehouse_code", scanValues.getString("target_warehouse_code"));
            binJsonValues.put("warehouse_id", scanValues.getLong("target_warehouse_id"));
            binJsonValues.put("code", scanValues.getString("target_bin_code"));
            binJsonValues.put("id", scanValues.get("target_bin_id"));
            Long binId = scanValues.getLong("target_bin_id");
            JSONObject materialJson = fetchOneMaterial(scanValues.getString("material_bcode"));
            JSONObject inBoundStockDetail = buildInBoundStockDetail(20, containerId, scan.getId(), batchNumber, paramJson, binId, binJsonValues, materialJson);
            inBoundDetailList.add(inBoundStockDetail);
        });
        logicInstanceDslService.batchAddOrUpdate(inBoundDetailList);
    }

    /**
     * 根据明细ID出库
     * 1. 扣减库存数据
     * 2. 添加出库记录
     * @param dataJson
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<JSONObject> outBoundByUniqueCodeList(JSONObject dataJson) {
        // 出库时的扫码明细，每条代表一货一码中的一条数据,只有唯一码即可
        Long formId = dataJson.getLong("formId");
        Integer formType = dataJson.getInteger("formType");
        List<String> uniqueCodeList = new ArrayList(dataJson.getJSONArray("uniqueCodeList"));
        Map<String, Long> stockDetailMap = fetchInBoundDetailIdByUniqueCodeList(uniqueCodeList);
        List<String> dbUniqueCodeList = stockDetailMap.keySet().stream().collect(Collectors.toList());
        if (uniqueCodeList.size() != dbUniqueCodeList.size()) {
            List<String> diffList = CollectionUtil.subtractToList(uniqueCodeList, dbUniqueCodeList);
            throwException("扫码数据与库存数据不一致,唯一码为: " +  diffList.stream().collect(Collectors.joining(",")));
        }
        List<Long> stockDetailIdList = stockDetailMap.values().stream().collect(Collectors.toList());
        return outBoundByIdList(stockDetailIdList, formId, formType);
    }

    /**
     * 根据明细ID出库
     * 1. 扣减库存数据
     * 2. 添加出库记录
     * @param dataJson
     * @return 返回出库的记录
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<JSONObject> outBoundByUniqueCodeOrKanbanCodeAndQuantity(JSONObject dataJson) {
        Long formId = dataJson.getLong("formId");
        Integer formType = dataJson.getInteger("formType");
        List<JSONObject> outBoundList = new ArrayList(dataJson.getJSONArray("outBoundList"));
        if (CollectionUtil.isEmpty(outBoundList)) {
            Collections.emptyList();
        }
        List<String> uniqueCodeList = outBoundList.stream().filter(d -> StrUtil.isNotBlank(d.getString("uniqueCode")))
                .map(d -> d.getString("uniqueCode")).collect(Collectors.toList());
        Map<String, BigDecimal> uniqueCodeQuantityMap = outBoundList.stream().filter(d -> StrUtil.isNotBlank(d.getString("uniqueCode")))
                .collect(Collectors.toMap(d -> d.getString("uniqueCode"), d->d.getBigDecimal("quantity")));
        Map<String, JSONObject> inBoundDetailMap = fetchInBoundDetailByUniqueCodeList(uniqueCodeList);

        List<String> kanbanCodeList = outBoundList.stream().filter(d -> StrUtil.isNotBlank(d.getString("kanbancode")))
                .map(d -> d.getString("kanbancode")).collect(Collectors.toList());
        Map<String, BigDecimal> kanbanCodeQuantityMap = outBoundList.stream().filter(d -> StrUtil.isNotBlank(d.getString("kanbancode")))
                .collect(Collectors.toMap(d -> d.getString("kanbancode"), d->d.getBigDecimal("quantity")));
        Map<String, JSONObject> kanbanCodeInBoundDetailMap = fetchInBoundDetailByKanbanCodeList(kanbanCodeList);

        // 将所有需要出库的数据，放到一起
        inBoundDetailMap.putAll(kanbanCodeInBoundDetailMap);

        List<JSONObject> waitUpdateInBoundDetailList = new ArrayList<>();
        List<JSONObject> waitAddOutBoundDetailList = new ArrayList<>();
        uniqueCodeQuantityMap.forEach((uniqueCode, quantity) -> {
            JSONObject inBoundDetail = inBoundDetailMap.get(uniqueCode);
            JSONObject values = updateQuantity(quantity, inBoundDetail);
            inBoundDetail.put("values", values);
            waitUpdateInBoundDetailList.add(inBoundDetail);

            JSONObject waitAddOutBoundDetail = buildOutBoundFromInbound(formId, formType, quantity, inBoundDetail);
            waitAddOutBoundDetailList.add(waitAddOutBoundDetail);
        });

        kanbanCodeQuantityMap.forEach((kanbanCode, quantity) -> {
            JSONObject inBoundDetail = inBoundDetailMap.get(kanbanCode);
            if (CollectionUtil.isEmpty(inBoundDetail)) {
                throwException("看板号"+kanbanCode+"不存在");
            }
            JSONObject values = updateQuantity(quantity, inBoundDetail);
            inBoundDetail.put("values", values);
            waitUpdateInBoundDetailList.add(inBoundDetail);

            JSONObject waitAddOutBoundDetail = buildOutBoundFromInbound(formId, formType, quantity, inBoundDetail);
            waitAddOutBoundDetailList.add(waitAddOutBoundDetail);
        });

        logicInstanceDslService.updateByIds(waitUpdateInBoundDetailList);
        logicInstanceDslService.batchAddOrUpdate(waitAddOutBoundDetailList);
        return waitAddOutBoundDetailList;
    }

    /**
     * 更新出库的主数量
     * @param quantity
     * @param inBoundDetail
     * @return
     */
    private JSONObject updateQuantity(BigDecimal quantity, JSONObject inBoundDetail) {
        JSONObject values = inBoundDetail.getJSONObject("values");
        // 实际库存数
        BigDecimal actualQuantity = values.getBigDecimal("quantity");
        if (actualQuantity.compareTo(quantity) < 0) {
            throwException(values.getString("material_bcode") + "库存数量不足，无法出库");
        }
        if (actualQuantity.compareTo(quantity) == 0) {
            // 说明需要全部出库
            values.put("available_subQuantity", BigDecimal.ZERO);
        }
        values.put("available_quantity", actualQuantity.subtract(quantity));
        return values;
    }

    /**
     * 根据入库记录，创建出库记录
     * @param formId
     * @param formType
     * @param quantity
     * @param inBoundDetail
     * @return
     */
    private JSONObject buildOutBoundFromInbound(Long formId, Integer formType, BigDecimal quantity, JSONObject inBoundDetail) {
        JSONObject waitAddOutBoundDetail = BeanUtil.copyProperties(inBoundDetail, JSONObject.class);
        waitAddOutBoundDetail.put("id", null);
        waitAddOutBoundDetail.put("createTime", null);
        waitAddOutBoundDetail.put("updateTime", null);
        waitAddOutBoundDetail.put("createdBy", null);
        waitAddOutBoundDetail.put("updatedBy", null);
        JSONObject waitAddOutBoundDetailValues = waitAddOutBoundDetail.getJSONObject("values");
        waitAddOutBoundDetailValues.put("stock_type", 2);
        waitAddOutBoundDetailValues.put("form_id", formId);
        waitAddOutBoundDetailValues.put("form_type", formType);
        waitAddOutBoundDetailValues.put("form_sub_id", inBoundDetail.getLong("id"));
        waitAddOutBoundDetailValues.put("available_quantity",BigDecimal.ZERO);
        waitAddOutBoundDetailValues.put("available_subQuantity", BigDecimal.ZERO);
        waitAddOutBoundDetailValues.put("quantity", quantity);
        waitAddOutBoundDetailValues.put("subQuantity", BigDecimal.ONE);
        waitAddOutBoundDetail.put("values", waitAddOutBoundDetailValues);
        return waitAddOutBoundDetail;
    }


    /**
     * 根据明细ID出库
     * 1. 扣减库存数据
     * 2. 添加出库记录
     * @param stockDetailIdList
     * @return 返回出库的记录
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<JSONObject> outBoundByIdList(List<Long> stockDetailIdList, Long formId, Integer formType) {
        if (CollectionUtil.isEmpty(stockDetailIdList)) {
            return Collections.emptyList();
        }
        // 扣减库存数据
        JSONObject paramJson = new JSONObject();
        paramJson.put("ids", stockDetailIdList);
        JSONObject values = new JSONObject();
        values.put("available_subQuantity", BigDecimal.ZERO);
        values.put("available_quantity", BigDecimal.ZERO);
        paramJson.put("values", values);
        logicInstanceDslService.updateByBatchId(paramJson);

        // 添加出库记录
        // 查询出所有被扣减库存的记录
        List<JSONObject> stockDetailList = logicInstanceDslService.queryJsonByIdList(stockDetailIdList);
        List<JSONObject> outBoundDetailLst = stockDetailList.stream().map(d -> {
            d.put("createTime", null);
            d.put("updateTime", null);
            d.put("createdBy", null);
            d.put("updatedBy", null);
            JSONObject outBoundValues = d.getJSONObject("values");
            outBoundValues.put("stock_type", 2);
            outBoundValues.put("form_id", formId);
            outBoundValues.put("form_type", formType);
            outBoundValues.put("form_sub_id", d.getLong("id"));
            d.remove("id");
            return d;
        }).collect(Collectors.toList());
        logicInstanceDslService.batchAddOrUpdate(BeanUtil.copyToList(outBoundDetailLst, JSONObject.class));
        updateKanbanToUnAvailable(outBoundDetailLst);
        return outBoundDetailLst;
    }
    /**
     * 根据明细ID入库，适用以下业务
     * 1. 倒库单，根据指定的库存记录，入到另一个库
     * @param stockDetailList
     * @return 返回出库的记录
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<JSONObject> inBoundByStockDetail(List<JSONObject> stockDetailList,
                                                 Long formId, Integer formType,
                                                 String inWarehouseCode, Long inWarehouseId,
                                                 String inBinCode, Long inBinId) {
        if (CollectionUtil.isEmpty(stockDetailList)) {
            return Collections.emptyList();
        }
        List<JSONObject> inBoundList = stockDetailList.stream().map(d -> {
            d.put("createTime", null);
            d.put("updateTime", null);
            d.put("createdBy", null);
            d.put("updatedBy", null);
            d.put("id", null);
            JSONObject values = d.getJSONObject("values");
            values.put("form_id", formId);
            values.put("form_type", formType);
            values.put("form_sub_id", null);
            values.put("warehouse_code", inWarehouseCode);
            values.put("warehouse_id", inWarehouseId);
            values.put("bin_code", inBinCode);
            values.put("bin_id", inBinId);
            return d;
        }).collect(Collectors.toList());
        logicInstanceDslService.batchAddOrUpdate(inBoundList);
        updateKanbanToAvailable(inBoundList);
        return inBoundList;
    }


    /**
     * 查询仓库库存明细列表
     * @param warehouseId
     * @return
     */
    public List<Long> fetchStockDetailIdList(Long warehouseId) {
        String condition = "warehouse_id = " + warehouseId + " and available_subQuantity > 0 and stock_type = 1";
        String className = "stockDetail";
        return logicInstanceDslService.queryIdWithoutHier(condition, className);
    }

    /**
     * 根据唯一码，查询当前仓库库存明细
     * @param paramJson 唯一码明细
     * @return
     */
    @Override
    public JSONObject queryAvailableByUniqueCode(JSONObject paramJson) {
        String uniqueCode = paramJson.getString("uniqueCode");
        return fetchAvailableByUniqueCode(uniqueCode);
    }

    /**
     * 根据唯一码或是看板码，查询当前仓库库存明细
     * @param uniqueCode 唯一码明细
     * @return
     */
    public JSONObject fetchAvailableByUniqueCode(String uniqueCode) {
        List<JSONObject> stockDetailList = null;
        String condition = "stock_type = 1 and status <> 9 and freezeStatus = 0 and uniqueCode = '" + uniqueCode + "' and available_subQuantity > 0";
        String className = "stockDetail";
        String orderBy = "id,desc";
        stockDetailList = logicInstanceDslService.queryWithoutHier(condition, className, orderBy);
        if (CollectionUtil.isEmpty(stockDetailList)) {
            condition = "stock_type = 1 and status <> 9 and freezeStatus = 0 and kanbancode = '" + uniqueCode + "' and available_subQuantity > 0";
            stockDetailList = logicInstanceDslService.queryWithoutHier(condition, className, orderBy);
        }
        if (CollectionUtil.isNotEmpty(stockDetailList)) {
            return stockDetailList.get(0);
        } else {
            return null;
        }
    }

    /**
     * 根据唯一码，查询最后一条出入库记录
     * @param uniqueCode 唯一码明细
     * @return
     */
    public JSONObject fetchLastByUniqueCode(String uniqueCode) {
        String condition = " status <> 9 and uniqueCode = '" + uniqueCode + "'";
        String className = "stockDetail";
        String orderBy = "id,desc";
        List<JSONObject> stockDetailList = logicInstanceDslService.queryWithoutHier(condition, className, orderBy);
        if (CollectionUtil.isNotEmpty(stockDetailList)) {
            return stockDetailList.get(0);
        } else {
            return null;
        }
    }

    /**
     * 查询仓库有效库存明细列表
     * @param uniqueCodeList 唯一码明细
     * @return  Map<String, Long>  key=uniqueCode, value=id
     */
    public Map<String, Long> fetchInBoundDetailIdByUniqueCodeList(List<String> uniqueCodeList) {
        String uniqueCodeListStr = uniqueCodeList.stream().map(s -> "'" + s + "'").collect(Collectors.joining(","));
        String condition = "uniqueCode in (" + uniqueCodeListStr + ") and available_subQuantity > 0 and stock_type = 1";
        String className = "stockDetail";
        List<JSONObject> detailList = logicInstanceDslService.queryWithoutHier(condition, className, "id,desc");
        return detailList.stream().collect(Collectors.toMap(d->d.getJSONObject("values").getString("uniqueCode"),
                d->d.getLong("id"), (k1,k2)->k1));
    }


    /**
     * 查询仓库有效库存明细列表
     * @param uniqueCodeList 唯一码明细
     * @return  Map<String, Long>  key=uniqueCode, value=id
     */
    public Map<String, JSONObject> fetchInBoundDetailByUniqueCodeList(List<String> uniqueCodeList) {
        if (CollectionUtil.isEmpty(uniqueCodeList)) {
            return MapUtil.newHashMap();
        }
        String uniqueCodeListStr = uniqueCodeList.stream().map(s -> "'" + s + "'").collect(Collectors.joining(","));
        String condition = "uniqueCode in (" + uniqueCodeListStr + ") and available_subQuantity > 0 and stock_type = 1";
        String className = "stockDetail";
        List<JSONObject> detailList = logicInstanceDslService.queryWithoutHier(condition, className, "id,desc");
        return detailList.stream().collect(Collectors.toMap(d->d.getJSONObject("values").getString("uniqueCode"),
                d->d, (k1,k2)->k1));
    }
    /**
     * 查询仓库有效库存明细列表
     * @param kanbanCodeList 唯一码明细
     * @return  Map<String, Long>  key=uniqueCode, value=id
     */
    public Map<String, JSONObject> fetchInBoundDetailByKanbanCodeList(List<String> kanbanCodeList) {
        if (CollectionUtil.isEmpty(kanbanCodeList)) {
            return MapUtil.newHashMap();
        }
        String uniqueCodeListStr = kanbanCodeList.stream().map(s -> "'" + s + "'").collect(Collectors.joining(","));
        String condition = "kanbancode in (" + uniqueCodeListStr + ") and available_subQuantity > 0 and stock_type = 1";
        String className = "stockDetail";
        List<JSONObject> detailList = logicInstanceDslService.queryWithoutHier(condition, className, "id,desc");
        return detailList.stream().collect(Collectors.toMap(d->d.getJSONObject("values").getString("kanbancode"),
                d->d, (k1,k2)->k1));
    }

    /**
     * 查询仓库出库列表
     * @param uniqueCodeList 唯一码明细
     * @return  Map<String, JSONObject>  key=uniqueCode, value=id
     */
    public Map<String, JSONObject> fetchOutBoundDetailListByUniqueCodeList(List<String> uniqueCodeList) {
        String uniqueCodeListStr = uniqueCodeList.stream().map(s -> "'" + s + "'").collect(Collectors.joining(","));
        String condition = "uniqueCode in (" + uniqueCodeListStr + ") and stock_type = 2 ";
        String className = "stockDetail";
        List<JSONObject> detailList = logicInstanceDslService.queryWithoutHier(condition, className, "id,desc");
        return detailList.stream().collect(Collectors.toMap(d->d.getJSONObject("values").getString("uniqueCode"),
                d->d, (k1,k2)->k1));
    }

    /**
     * 冻结库存批次
     * @param dataJson
     */
    @Override
    public void freezeBatchNumber(JSONObject dataJson) {
        updateBatchNumberStatus(dataJson, 1);
    }
    /**
     * 解冻库存批次
     * @param dataJson
     */
    @Override
    public void unFreezeBatchNumber(JSONObject dataJson) {
        updateBatchNumberStatus(dataJson, 0);
    }

    /**
     * 批量更新库存状态为已检验状态
     * @param dataJson
     */
    @Override
    public void batchUpdateStockStatus(JSONObject dataJson) {
        String materialBcode = dataJson.getString("material_bcode");
        String className = dataJson.getString("className");
        Integer status = dataJson.getInteger("status");
        Integer warehouseId = dataJson.getInteger("warehouse_id");
        String kanbanCodeStr = dataJson.getString("kanbancodes");
        if (StrUtil.isBlank(kanbanCodeStr)) {
            return;
        }
        List<String> kanbanCodeList = Arrays.stream(kanbanCodeStr.split(",")).collect(Collectors.toList());
        String kanbanCodeClause = "('" + String.join("', '", kanbanCodeList) + "')";
        String condition = " available_quantity > 0 and status = 0  and material_bcode = '" + materialBcode + "' and warehouse_id = " + warehouseId + " and kanbancode in " + kanbanCodeClause;
        JSONObject values = new JSONObject();
        values.put("status", status);
        if(StrUtil.isNotBlank(condition)){
            List<Long> ids = logicInstanceDslService.queryIdWithoutHier(condition, className);
            JSONObject data = new JSONObject();
            data.put("ids",ids);
            data.put("values",values);
            logicInstanceDslService.updateByBatchId(data);
            //logicInstanceDslService.updateByCondition(className, condition, values);
        }
    }

    /**
     * 将库存批次号更新为指定状态
     * @param dataJson
     * @param i
     */
    private void updateBatchNumberStatus(JSONObject dataJson, int i) {
        String materialBcode = dataJson.getString("material_bcode");
        String batchNumber = dataJson.getString("batch_number");
        String className = "stockDetail";
        String condition = "material_bcode = '" + materialBcode + "' and batch_number = '" + batchNumber + "'";
        JSONObject updateJson = new JSONObject();
        JSONObject valueJson = new JSONObject();
        valueJson.put("freezeStatus", i);
        updateJson.put("values", valueJson);
        logicInstanceDslService.updateByCondition(className, condition, updateJson);
        Long batchFreezeId = dataJson.getLong("batchFreezeId");
        JSONObject freezeUpdateJson = new JSONObject();
        JSONObject freezeValueJson = new JSONObject();
        freezeValueJson.put("freezeStatus", i);
        freezeUpdateJson.put("values", valueJson);
        freezeUpdateJson.put("id", batchFreezeId);
        logicInstanceDslService.updateById(freezeUpdateJson);
    }

}
